diff --git a/.flutter b/.flutter
index b0850beeb..80c2e8497 160000
--- a/.flutter
+++ b/.flutter
@@ -1 +1 @@
-Subproject commit b0850beeb25f6d5b10426284f506557f66181b36
+Subproject commit 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ef7801219..17221f1e8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -53,6 +53,8 @@ jobs:
./flutterw build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi
cp build/app/outputs/apk/izzy/release/*.apk outputs
scripts/apply_flavor_libre.sh
+ ./flutterw build appbundle -t lib/main_libre.dart --flavor libre
+ cp build/app/outputs/bundle/libreRelease/*.aab outputs
./flutterw build apk -t lib/main_libre.dart --flavor libre --split-per-abi
cp build/app/outputs/apk/libre/release/*.apk outputs
rm $AVES_STORE_FILE
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e355108e8..cee23d87f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.11.9] - 2024-08-07
+
+### Added
+
+- Viewer: display more items in tag/copy/move quick action choosers
+- Viewer: long descriptions are scrollable when overlay is expanded by tap
+- Collection: sort by duration
+- Map: open external map app from map views
+- Explorer: stats
+
+### Changed
+
+- Accessibility: more animations and effects are suppressed when animations are disabled
+- upgraded Flutter to stable v3.24.0
+
+### Fixed
+
+- opening app from launcher always showing home page
+- collection quick actions not showing in the top bar nor the menu
+- multiple widget setup after device reboot
+
## [v1.11.8] - 2024-07-19
### Added
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 1a9404a93..2a68ac2c7 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -189,7 +189,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:1.7.0"
implementation 'androidx.core:core-ktx:1.13.1'
- implementation 'androidx.lifecycle:lifecycle-process:2.8.3'
+ implementation 'androidx.lifecycle:lifecycle-process:2.8.4'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
@@ -201,7 +201,7 @@ dependencies {
implementation "com.github.bumptech.glide:glide:$glide_version"
implementation 'com.google.android.material:material:1.12.0'
// SLF4J implementation for `mp4parser`
- implementation 'org.slf4j:slf4j-simple:2.0.13'
+ implementation 'org.slf4j:slf4j-simple:2.0.14'
// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
@@ -213,9 +213,9 @@ dependencies {
implementation 'com.github.deckerst:pixymeta-android:9ec7097f17'
implementation project(':exifinterface')
- testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
+ testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.3'
- kapt 'androidx.annotation:annotation:1.8.0'
+ kapt 'androidx.annotation:annotation:1.8.1'
ksp "com.github.bumptech.glide:ksp:$glide_version"
compileOnly rootProject.findProject(':streams_channel')
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 5f596ade3..a83c2ec03 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -323,8 +323,10 @@
-
+
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 079ad5d30..f42bb2e8e 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
@@ -11,12 +11,18 @@ import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
import android.util.Log
import android.util.SizeF
import android.widget.RemoteViews
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
-import deckers.thibault.aves.channel.calls.*
+import deckers.thibault.aves.channel.calls.DeviceHandler
+import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
+import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
+import deckers.thibault.aves.channel.calls.MediaStoreHandler
+import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.model.FieldMap
@@ -26,8 +32,14 @@ import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import java.nio.ByteBuffer
+import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@@ -108,37 +120,25 @@ class HomeWidgetProvider : AppWidgetProvider() {
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
+ val params = hashMapOf(
+ "widgetId" to widgetId,
+ "sizesDip" to sizesDip,
+ "devicePixelRatio" to getDevicePixelRatio(),
+ "drawEntryImage" to drawEntryImage,
+ "reuseEntry" to reuseEntry,
+ "isSystemThemeDark" to isNightModeOn,
+ ).apply {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius))
+ }
+ }
+
initFlutterEngine(context)
- val messenger = flutterEngine!!.dartExecutor
- val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
try {
- val props = suspendCoroutine { cont ->
+ val props = suspendCoroutine { cont ->
defaultScope.launch {
FlutterUtils.runOnUiThread {
- channel.invokeMethod("drawWidget", hashMapOf(
- "widgetId" to widgetId,
- "sizesDip" to sizesDip,
- "devicePixelRatio" to getDevicePixelRatio(),
- "drawEntryImage" to drawEntryImage,
- "reuseEntry" to reuseEntry,
- "isSystemThemeDark" to isNightModeOn,
- ).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)
- }
-
- override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
- cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
- }
-
- override fun notImplemented() {
- cont.resumeWithException(Exception("not implemented"))
- }
- })
+ tryDrawWidget(params, cont, 0)
}
}
}
@@ -150,6 +150,30 @@ class HomeWidgetProvider : AppWidgetProvider() {
return null
}
+ private fun tryDrawWidget(params: HashMap, cont: Continuation, drawRetry: Int) {
+ val messenger = flutterEngine!!.dartExecutor
+ val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
+ channel.invokeMethod("drawWidget", params, object : MethodChannel.Result {
+ override fun success(result: Any?) {
+ cont.resume(result)
+ }
+
+ override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
+ cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
+ }
+
+ override fun notImplemented() {
+ if (drawRetry > DRAW_RETRY_MAX) {
+ cont.resumeWithException(Exception("not implemented"))
+ } else {
+ Handler(Looper.getMainLooper()).postDelayed({
+ tryDrawWidget(params, cont, drawRetry + 1)
+ }, 2000L)
+ }
+ }
+ })
+ }
+
private fun updateWidgetImage(
context: Context,
appWidgetManager: AppWidgetManager,
@@ -271,6 +295,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
private val LOG_TAG = LogUtils.createTag()
private const val WIDGET_DART_ENTRYPOINT = "widgetMain"
private const val WIDGET_DRAW_CHANNEL = "deckers.thibault/aves/widget_draw"
+ private const val DRAW_RETRY_MAX = 5
private var flutterEngine: FlutterEngine? = null
private var imageByteFetchJob: Job? = null
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaStoreHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaStoreHandler.kt
index cc787ef7d..da82657a4 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaStoreHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaStoreHandler.kt
@@ -59,7 +59,14 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
private fun getGeneration(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val generation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- MediaStore.getGeneration(context, MediaStore.VOLUME_EXTERNAL_PRIMARY)
+ try {
+ MediaStore.getGeneration(context, MediaStore.VOLUME_EXTERNAL_PRIMARY)
+ } catch (e: Exception) {
+ // may yield `IllegalArgumentException: Volume external_primary not found`
+ val volumes = MediaStore.getExternalVolumeNames(context).joinToString(", ")
+ result.error("getGeneration-primary", e.message + " (available volumes are $volumes)", e)
+ return
+ }
} else {
null
}
diff --git a/android/app/src/main/res/values-nl/strings.xml b/android/app/src/main/res/values-nl/strings.xml
index 4e5ba3da4..46db9f734 100644
--- a/android/app/src/main/res/values-nl/strings.xml
+++ b/android/app/src/main/res/values-nl/strings.xml
@@ -6,7 +6,7 @@
Zoeken
Video’s
Media indexeren
- Indexeren van media
+ Media indexeren
Stoppen
Veilige modus
\ No newline at end of file
diff --git a/android/app/src/main/res/values-sv/strings.xml b/android/app/src/main/res/values-sv/strings.xml
index 786d17c9a..6047556e8 100644
--- a/android/app/src/main/res/values-sv/strings.xml
+++ b/android/app/src/main/res/values-sv/strings.xml
@@ -7,6 +7,6 @@
Videor
Media scanning
Scannar media
- Stop
+ Stopp
Sök
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/120.txt b/fastlane/metadata/android/en-US/changelogs/120.txt
deleted file mode 100644
index f487944d6..000000000
--- a/fastlane/metadata/android/en-US/changelogs/120.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.11.1:
-- watch videos with SRT subtitle files
-- enjoy the app in Persian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/12001.txt b/fastlane/metadata/android/en-US/changelogs/12001.txt
deleted file mode 100644
index f487944d6..000000000
--- a/fastlane/metadata/android/en-US/changelogs/12001.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.11.1:
-- watch videos with SRT subtitle files
-- enjoy the app in Persian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/121.txt b/fastlane/metadata/android/en-US/changelogs/121.txt
deleted file mode 100644
index 24d17aed2..000000000
--- a/fastlane/metadata/android/en-US/changelogs/121.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.11.2:
-- show selected albums together in Collection
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/12101.txt b/fastlane/metadata/android/en-US/changelogs/12101.txt
deleted file mode 100644
index 24d17aed2..000000000
--- a/fastlane/metadata/android/en-US/changelogs/12101.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.11.2:
-- show selected albums together in Collection
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/122.txt b/fastlane/metadata/android/en-US/changelogs/122.txt
deleted file mode 100644
index dfb8b7c93..000000000
--- a/fastlane/metadata/android/en-US/changelogs/122.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.11.3:
-- show selected albums together in Collection
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/12201.txt b/fastlane/metadata/android/en-US/changelogs/12201.txt
deleted file mode 100644
index dfb8b7c93..000000000
--- a/fastlane/metadata/android/en-US/changelogs/12201.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.11.3:
-- show selected albums together in Collection
-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
deleted file mode 100644
index b1151387e..000000000
--- a/fastlane/metadata/android/en-US/changelogs/123.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-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
deleted file mode 100644
index b1151387e..000000000
--- a/fastlane/metadata/android/en-US/changelogs/12301.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-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/128.txt b/fastlane/metadata/android/en-US/changelogs/128.txt
new file mode 100644
index 000000000..1b597dbb2
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/128.txt
@@ -0,0 +1,5 @@
+In v1.11.9:
+- peruse more options to tag or move via quick actions
+- read long descriptions right from the overlay
+- sort videos by duration
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/12801.txt b/fastlane/metadata/android/en-US/changelogs/12801.txt
new file mode 100644
index 000000000..1b597dbb2
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/12801.txt
@@ -0,0 +1,5 @@
+In v1.11.9:
+- peruse more options to tag or move via quick actions
+- read long descriptions right from the overlay
+- sort videos by duration
+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 7caea6927..9d9230fe7 100644
--- a/lib/l10n/app_ar.arb
+++ b/lib/l10n/app_ar.arb
@@ -1536,5 +1536,17 @@
"chipActionGoToExplorerPage": "عرض في المستكشف",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "المستكشف",
- "@explorerPageTitle": {}
+ "@explorerPageTitle": {},
+ "explorerActionSelectStorageVolume": "حدد التخزين",
+ "@explorerActionSelectStorageVolume": {},
+ "setHomeCustom": "مخصص",
+ "@setHomeCustom": {},
+ "selectStorageVolumeDialogTitle": "حدد التَخزين",
+ "@selectStorageVolumeDialogTitle": {},
+ "sortOrderShortestFirst": "الأقصر أولاً",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "الأطول أولاً",
+ "@sortOrderLongestFirst": {},
+ "sortByDuration": "حسب المدة",
+ "@sortByDuration": {}
}
diff --git a/lib/l10n/app_be.arb b/lib/l10n/app_be.arb
index 3b4f8304c..4ef9cd11e 100644
--- a/lib/l10n/app_be.arb
+++ b/lib/l10n/app_be.arb
@@ -1536,5 +1536,17 @@
"chipActionGoToExplorerPage": "Паказаць у Правадыру",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Правадыр",
- "@explorerPageTitle": {}
+ "@explorerPageTitle": {},
+ "sortByDuration": "Па працягласці",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "Спачатку самы кароткі",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "Спачатку самы доўгі",
+ "@sortOrderLongestFirst": {},
+ "explorerActionSelectStorageVolume": "Выбраць сховішча",
+ "@explorerActionSelectStorageVolume": {},
+ "selectStorageVolumeDialogTitle": "Выбраць сховішча",
+ "@selectStorageVolumeDialogTitle": {},
+ "setHomeCustom": "Па-свойму",
+ "@setHomeCustom": {}
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index f7dbac54c..bc217fb55 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -723,6 +723,7 @@
"sortBySize": "By size",
"sortByAlbumFileName": "By album & file name",
"sortByRating": "By rating",
+ "sortByDuration": "By duration",
"sortOrderNewestFirst": "Newest first",
"sortOrderOldestFirst": "Oldest first",
@@ -732,6 +733,8 @@
"sortOrderLowestFirst": "Lowest first",
"sortOrderLargestFirst": "Largest first",
"sortOrderSmallestFirst": "Smallest first",
+ "sortOrderShortestFirst": "Shortest first",
+ "sortOrderLongestFirst": "Longest first",
"albumGroupTier": "By tier",
"albumGroupType": "By type",
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index e222d0467..21a83d5bf 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -1384,5 +1384,11 @@
"setHomeCustom": "Personalizado",
"@setHomeCustom": {},
"explorerActionSelectStorageVolume": "Seleccionar almacenamiento",
- "@explorerActionSelectStorageVolume": {}
+ "@explorerActionSelectStorageVolume": {},
+ "sortByDuration": "Por duración",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "El más corto primero",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "El más largo primero",
+ "@sortOrderLongestFirst": {}
}
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 0b9c7ed12..a7b67fe3f 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -1384,5 +1384,11 @@
"explorerActionSelectStorageVolume": "Choisir le stockage",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Volumes de stockage",
- "@selectStorageVolumeDialogTitle": {}
+ "@selectStorageVolumeDialogTitle": {},
+ "sortByDuration": "par durée",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "Plus courts d’abord",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "Plus longs d’abord",
+ "@sortOrderLongestFirst": {}
}
diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb
index e11454b4b..316e907b9 100644
--- a/lib/l10n/app_hi.arb
+++ b/lib/l10n/app_hi.arb
@@ -101,5 +101,448 @@
"chipActionGoToCountryPage": "देशों में दिखाएं",
"@chipActionGoToCountryPage": {},
"chipActionHide": "छिपाए",
- "@chipActionHide": {}
+ "@chipActionHide": {},
+ "chipActionShowCollection": "कोलेक्शन में दिखाए",
+ "@chipActionShowCollection": {},
+ "chipActionFilterOut": "फिल्टर करें",
+ "@chipActionFilterOut": {},
+ "chipActionLock": "लॉक",
+ "@chipActionLock": {},
+ "chipActionPin": "शीर्ष पर पिन करें",
+ "@chipActionPin": {},
+ "chipActionGoToExplorerPage": "एक्सप्लोरर में दिखाए",
+ "@chipActionGoToExplorerPage": {},
+ "entryActionRotateCW": "दक्षिणावर्त घुमाएं",
+ "@entryActionRotateCW": {},
+ "entryActionViewSource": "सोर्स देखें",
+ "@entryActionViewSource": {},
+ "entryActionShowGeoTiffOnMap": "मैप ओवरले के रूप में देखे",
+ "@entryActionShowGeoTiffOnMap": {},
+ "entryActionViewMotionPhotoVideo": "वीडियो खोलें",
+ "@entryActionViewMotionPhotoVideo": {},
+ "entryActionOpen": "के साथ खोलें",
+ "@entryActionOpen": {},
+ "entryActionRemoveFavourite": "पसंदीदा से निकालें",
+ "@entryActionRemoveFavourite": {},
+ "entryInfoActionRemoveMetadata": "मेटाडाटा हटाएं",
+ "@entryInfoActionRemoveMetadata": {},
+ "entryInfoActionRemoveLocation": "लोकेशन हटाएं",
+ "@entryInfoActionRemoveLocation": {},
+ "entryActionFlip": "क्षैतिज फ्लिप करे",
+ "@entryActionFlip": {},
+ "entryActionShareVideoOnly": "केवल वीडियो शेयर करें",
+ "@entryActionShareVideoOnly": {},
+ "entryActionConvertMotionPhotoToStillImage": "स्थिर छवि में परिवर्तित करें",
+ "@entryActionConvertMotionPhotoToStillImage": {},
+ "entryActionOpenMap": "मैप एप में दिखाएं",
+ "@entryActionOpenMap": {},
+ "entryActionSetAs": "के रूप में सेट करें",
+ "@entryActionSetAs": {},
+ "entryActionRotateScreen": "स्क्रीन घुमाएँ",
+ "@entryActionRotateScreen": {},
+ "videoActionCaptureFrame": "फ्रेम कैप्चर करें",
+ "@videoActionCaptureFrame": {},
+ "chipActionCreateAlbum": "एल्बम बनाएं",
+ "@chipActionCreateAlbum": {},
+ "chipActionCreateVault": "वॉल्ट बनाएं",
+ "@chipActionCreateVault": {},
+ "videoActionPlay": "चलाएं",
+ "@videoActionPlay": {},
+ "videoActionReplay10": "10 सेकंड्स पीछे ले",
+ "@videoActionReplay10": {},
+ "videoActionSkip10": "10 सेकंड्स आगे लें",
+ "@videoActionSkip10": {},
+ "videoActionUnmute": "अनम्यूट करे",
+ "@videoActionUnmute": {},
+ "slideshowActionShowInCollection": "संग्रह में दिखाएं",
+ "@slideshowActionShowInCollection": {},
+ "slideshowActionResume": "रिज्यूम करें",
+ "@slideshowActionResume": {},
+ "filterTypeAnimatedLabel": "एनिमेटेड",
+ "@filterTypeAnimatedLabel": {},
+ "filterTypeMotionPhotoLabel": "मोशन फोटो",
+ "@filterTypeMotionPhotoLabel": {},
+ "filterTypePanoramaLabel": "पैनोरमा",
+ "@filterTypePanoramaLabel": {},
+ "sourceStateLocatingCountries": "देश खोज रहे हैं",
+ "@sourceStateLocatingCountries": {},
+ "sourceStateLocatingPlaces": "स्थान खोज रहें हैं",
+ "@sourceStateLocatingPlaces": {},
+ "chipActionFilterIn": "में फिल्टर करें",
+ "@chipActionFilterIn": {},
+ "chipActionRename": "नाम बदले",
+ "@chipActionRename": {},
+ "chipActionUnpin": "शीर्ष से अनपिन करें",
+ "@chipActionUnpin": {},
+ "entryActionEdit": "एडिट करें",
+ "@entryActionEdit": {},
+ "videoActionMute": "म्यूट करे",
+ "@videoActionMute": {},
+ "videoActionPause": "रोके",
+ "@videoActionPause": {},
+ "entryInfoActionEditTags": "टैग्स एडिट करे",
+ "@entryInfoActionEditTags": {},
+ "filterOnThisDayLabel": "इस दिन पर",
+ "@filterOnThisDayLabel": {},
+ "filterTypeSphericalVideoLabel": "360° वीडियो",
+ "@filterTypeSphericalVideoLabel": {},
+ "filterMimeVideoLabel": "वीडियो",
+ "@filterMimeVideoLabel": {},
+ "viewerActionSettings": "सैटिंग",
+ "@viewerActionSettings": {},
+ "entryActionCast": "कास्ट करें",
+ "@entryActionCast": {},
+ "entryInfoActionExportMetadata": "मेटाडाटा एक्सपोर्ट करें",
+ "@entryInfoActionExportMetadata": {},
+ "chipActionSetCover": "कवर सेट करें",
+ "@chipActionSetCover": {},
+ "entryActionCopyToClipboard": "क्लिपबोर्ड पर कॉपी करें",
+ "@entryActionCopyToClipboard": {},
+ "entryActionDelete": "मिटाएं",
+ "@entryActionDelete": {},
+ "entryActionExport": "एक्सपोर्ट करें",
+ "@entryActionExport": {},
+ "entryActionInfo": "जानकारी",
+ "@entryActionInfo": {},
+ "entryActionConvert": "बदले",
+ "@entryActionConvert": {},
+ "entryActionRename": "नाम बदलें",
+ "@entryActionRename": {},
+ "entryActionRestore": "रिस्टोर करे",
+ "@entryActionRestore": {},
+ "entryActionRotateCCW": "वामावर्त स्थिति में घुमाएं",
+ "@entryActionRotateCCW": {},
+ "entryActionPrint": "प्रिंट करे",
+ "@entryActionPrint": {},
+ "entryActionShare": "शेयर करे",
+ "@entryActionShare": {},
+ "entryActionShareImageOnly": "केवल इमेज शेयर करें",
+ "@entryActionShareImageOnly": {},
+ "entryActionAddFavourite": "पसंदीदा में जोड़े",
+ "@entryActionAddFavourite": {},
+ "videoActionSelectStreams": "ट्रैक्स को चुने",
+ "@videoActionSelectStreams": {},
+ "videoActionSetSpeed": "चलाने की गति",
+ "@videoActionSetSpeed": {},
+ "entryInfoActionEditDate": "दिनांक व समय एडिट करे",
+ "@entryInfoActionEditDate": {},
+ "entryInfoActionEditLocation": "लोकेशन एडिट करे",
+ "@entryInfoActionEditLocation": {},
+ "entryInfoActionEditRating": "रेटिंग एडिट करे",
+ "@entryInfoActionEditRating": {},
+ "editorTransformRotate": "घुमाएं",
+ "@editorTransformRotate": {},
+ "cropAspectRatioOriginal": "ओरिजनल",
+ "@cropAspectRatioOriginal": {},
+ "filterFavouriteLabel": "पसंदीदा",
+ "@filterFavouriteLabel": {},
+ "filterRecentlyAddedLabel": "हाल ही में शामिल की गई",
+ "@filterRecentlyAddedLabel": {},
+ "filterMimeImageLabel": "इमेज",
+ "@filterMimeImageLabel": {},
+ "keepScreenOnVideoPlayback": "वीडियो प्लेबैक के दौरान",
+ "@keepScreenOnVideoPlayback": {},
+ "displayRefreshRatePreferLowest": "न्यूनतम दर",
+ "@displayRefreshRatePreferLowest": {},
+ "nameConflictStrategyRename": "नाम बदलें",
+ "@nameConflictStrategyRename": {},
+ "unitSystemMetric": "Metric",
+ "@unitSystemMetric": {},
+ "viewerTransitionSlide": "स्लाइड",
+ "@viewerTransitionSlide": {},
+ "viewerTransitionFade": "फेड",
+ "@viewerTransitionFade": {},
+ "newAlbumDialogStorageLabel": "स्टोरेज:",
+ "@newAlbumDialogStorageLabel": {},
+ "newVaultDialogTitle": "नया वॉल्ट",
+ "@newVaultDialogTitle": {},
+ "vaultDialogLockModeWhenScreenOff": "लॉक करे,जब स्क्रीन बंद हो जाती है",
+ "@vaultDialogLockModeWhenScreenOff": {},
+ "filterTaggedLabel": "टैग किया गया",
+ "@filterTaggedLabel": {},
+ "mapStyleGoogleTerrain": "गूगल मैप्स (टेरेन)",
+ "@mapStyleGoogleTerrain": {},
+ "themeBrightnessDark": "Dark",
+ "@themeBrightnessDark": {},
+ "themeBrightnessBlack": "Black",
+ "@themeBrightnessBlack": {},
+ "videoControlsPlaySeek": "पिछड़े / आगे की तलाश करें",
+ "@videoControlsPlaySeek": {},
+ "mapStyleOsmHot": "Humanitarian OSM",
+ "@mapStyleOsmHot": {},
+ "filterAspectRatioPortraitLabel": "पोर्ट्रेट",
+ "@filterAspectRatioPortraitLabel": {},
+ "filterNoAddressLabel": "एड्रेस रहित",
+ "@filterNoAddressLabel": {},
+ "filterNoRatingLabel": "रेट नहीं किया गया",
+ "@filterNoRatingLabel": {},
+ "filterRatingRejectedLabel": "अस्वीकृत",
+ "@filterRatingRejectedLabel": {},
+ "mapStyleGoogleNormal": "गूगल मैप्स",
+ "@mapStyleGoogleNormal": {},
+ "mapStyleGoogleHybrid": "गूगल मैप्स (हाइब्रिड)",
+ "@mapStyleGoogleHybrid": {},
+ "mapStyleStamenWatercolor": "Stamen Watercolor",
+ "@mapStyleStamenWatercolor": {},
+ "unitSystemImperial": "Imperial",
+ "@unitSystemImperial": {},
+ "passwordDialogEnter": "पासवर्ड दर्ज करें",
+ "@passwordDialogEnter": {},
+ "filterBinLabel": "रीसाइकल बिन",
+ "@filterBinLabel": {},
+ "filterTypeRawLabel": "Raw",
+ "@filterTypeRawLabel": {},
+ "albumTierVaults": "संदूक",
+ "@albumTierVaults": {},
+ "albumTierRegular": "अन्य",
+ "@albumTierRegular": {},
+ "coordinateFormatDms": "DMS",
+ "@coordinateFormatDms": {},
+ "coordinateFormatDecimal": "Decimal degrees",
+ "@coordinateFormatDecimal": {},
+ "coordinateDms": "{coordinate} {direction}",
+ "@coordinateDms": {
+ "placeholders": {
+ "coordinate": {
+ "type": "String",
+ "example": "38° 41′ 47.72″"
+ },
+ "direction": {
+ "type": "String",
+ "example": "S"
+ }
+ }
+ },
+ "coordinateDmsSouth": "द",
+ "@coordinateDmsSouth": {},
+ "coordinateDmsWest": "प",
+ "@coordinateDmsWest": {},
+ "displayRefreshRatePreferHighest": "उच्चतम दर",
+ "@displayRefreshRatePreferHighest": {},
+ "lengthUnitPixel": "px",
+ "@lengthUnitPixel": {},
+ "subtitlePositionTop": "शीर्ष",
+ "@subtitlePositionTop": {},
+ "subtitlePositionBottom": "नीचे",
+ "@subtitlePositionBottom": {},
+ "videoLoopModeNever": "कभी नहीं",
+ "@videoLoopModeNever": {},
+ "videoPlaybackMuted": "बिना ध्वनि के चलाएं",
+ "@videoPlaybackMuted": {},
+ "viewerTransitionNone": "कोई नहीं",
+ "@viewerTransitionNone": {},
+ "nameConflictDialogSingleSourceMessage": "गंतव्य फ़ोल्डर में कुछ फ़ाइलों का नाम समान है।।",
+ "@nameConflictDialogSingleSourceMessage": {},
+ "noMatchingAppDialogMessage": "इसमें कोई ऐप नहीं है जो इसे संभाल सकता है।।",
+ "@noMatchingAppDialogMessage": {},
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{Move this item to the recycle bin?} other{Move these {count} items to the recycle bin?}}",
+ "@binEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {
+ "format": "decimalPattern"
+ }
+ }
+ },
+ "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Delete this item?} other{Delete these {count} items?}}",
+ "@deleteEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {
+ "format": "decimalPattern"
+ }
+ }
+ },
+ "newAlbumDialogTitle": "नया एल्बम",
+ "@newAlbumDialogTitle": {},
+ "newAlbumDialogNameLabel": "एल्बम का नाम",
+ "@newAlbumDialogNameLabel": {},
+ "pinDialogEnter": "पिन दर्ज करें",
+ "@pinDialogEnter": {},
+ "pinDialogConfirm": "पिन कन्फर्म करें",
+ "@pinDialogConfirm": {},
+ "passwordDialogConfirm": "पासवर्ड कन्फर्म करें",
+ "@passwordDialogConfirm": {},
+ "videoLoopModeAlways": "हमेशा",
+ "@videoLoopModeAlways": {},
+ "videoPlaybackSkip": "छोड़े",
+ "@videoPlaybackSkip": {},
+ "newAlbumDialogNameLabelAlreadyExistsHelper": "डायरेक्टरी पहले से मौजूद",
+ "@newAlbumDialogNameLabelAlreadyExistsHelper": {},
+ "coordinateDmsEast": "पू",
+ "@coordinateDmsEast": {},
+ "moveUndatedConfirmationDialogSetDate": "तारीख सहेजें",
+ "@moveUndatedConfirmationDialogSetDate": {},
+ "notEnoughSpaceDialogMessage": "इस ऑपरेशन को पूरा करने के लिए \"{volume}\" पर “{neededSize}” खाली जगह की आवश्यकता है, लेकिन केवल {freeSize} जगह है।।",
+ "@notEnoughSpaceDialogMessage": {
+ "placeholders": {
+ "neededSize": {
+ "type": "String",
+ "example": "314 MB"
+ },
+ "freeSize": {
+ "type": "String",
+ "example": "123 MB"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "renameAlbumDialogLabel": "नया नाम",
+ "@renameAlbumDialogLabel": {},
+ "wallpaperTargetLock": "लॉक स्क्रीन",
+ "@wallpaperTargetLock": {},
+ "unsupportedTypeDialogMessage": "{count, plural, =1{This operation is not supported for items of the following type: {types}.} other{This operation is not supported for items of the following types: {types}.}}",
+ "@unsupportedTypeDialogMessage": {
+ "placeholders": {
+ "count": {},
+ "types": {
+ "type": "String",
+ "example": "GIF, TIFF, MP4",
+ "description": "a list of unsupported types"
+ }
+ }
+ },
+ "restrictedAccessDialogMessage": "इस एप्लिकेशन को \"{volume}\" की {directory} में फ़ाइलों को संशोधित करने की अनुमति नहीं है।\n\nकृपया किसी अन्य directory में आइटम स्थानांतरित करने के लिए एक पूर्व-स्थापित फ़ाइल प्रबंधक या गैलरी ऐप का उपयोग करें।।",
+ "@restrictedAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "videoResumeDialogMessage": "क्या आप {time} पर पुन: चलाना चाहते हैं?",
+ "@videoResumeDialogMessage": {
+ "placeholders": {
+ "time": {
+ "type": "String",
+ "example": "13:37"
+ }
+ }
+ },
+ "videoStartOverButtonLabel": "पुन: प्रारंभ करें",
+ "@videoStartOverButtonLabel": {},
+ "vaultDialogLockTypeLabel": "लॉक प्रकार",
+ "@vaultDialogLockTypeLabel": {},
+ "patternDialogConfirm": "पैटर्न कन्फर्म करे",
+ "@patternDialogConfirm": {},
+ "patternDialogEnter": "पैटर्न दर्ज करें",
+ "@patternDialogEnter": {},
+ "authenticateToUnlockVault": "वॉल्ट को अनलॉक करने के लिए प्रमाणीकरण करें",
+ "@authenticateToUnlockVault": {},
+ "settingsVideoEnablePip": "पिक्चर-इन-पिक्चर",
+ "@settingsVideoEnablePip": {},
+ "videoControlsPlay": "चलाएं",
+ "@videoControlsPlay": {},
+ "videoControlsPlayOutside": "अन्य प्लेयर के साथ खोलें",
+ "@videoControlsPlayOutside": {},
+ "videoControlsNone": "कोई नहीं",
+ "@videoControlsNone": {},
+ "videoLoopModeShortOnly": "केवल लघु वीडियो",
+ "@videoLoopModeShortOnly": {},
+ "videoPlaybackWithSound": "ध्वनि के साथ चलाए",
+ "@videoPlaybackWithSound": {},
+ "viewerTransitionParallax": "पैरालैक्स",
+ "@viewerTransitionParallax": {},
+ "viewerTransitionZoomIn": "ज़ूम इन",
+ "@viewerTransitionZoomIn": {},
+ "wallpaperTargetHome": "होम स्क्रीन",
+ "@wallpaperTargetHome": {},
+ "widgetDisplayedItemRandom": "यादृच्छिक",
+ "@widgetDisplayedItemRandom": {},
+ "videoResumeButtonLabel": "पुन: चलाएं",
+ "@videoResumeButtonLabel": {},
+ "filterNoTitleLabel": "शीर्षकहीन",
+ "@filterNoTitleLabel": {},
+ "stopTooltip": "रोके",
+ "@stopTooltip": {},
+ "vaultLockTypePin": "पिन",
+ "@vaultLockTypePin": {},
+ "wallpaperTargetHomeLock": "होम और लॉक स्क्रीन",
+ "@wallpaperTargetHomeLock": {},
+ "widgetDisplayedItemMostRecent": "हाल ही के",
+ "@widgetDisplayedItemMostRecent": {},
+ "filterAspectRatioLandscapeLabel": "लैंडस्केप",
+ "@filterAspectRatioLandscapeLabel": {},
+ "filterNoDateLabel": "अदिनांकित",
+ "@filterNoDateLabel": {},
+ "filterNoTagLabel": "टैग नहीं किया गया",
+ "@filterNoTagLabel": {},
+ "filterTypeGeotiffLabel": "GeoTIFF",
+ "@filterTypeGeotiffLabel": {},
+ "accessibilityAnimationsKeep": "स्क्रीन प्रभाव रखें",
+ "@accessibilityAnimationsKeep": {},
+ "vaultLockTypePattern": "पैटर्न",
+ "@vaultLockTypePattern": {},
+ "coordinateDmsNorth": "उ",
+ "@coordinateDmsNorth": {},
+ "albumTierNew": "नया",
+ "@albumTierNew": {},
+ "albumTierApps": "ऐप्स",
+ "@albumTierApps": {},
+ "lengthUnitPercent": "%",
+ "@lengthUnitPercent": {},
+ "nameConflictStrategyReplace": "बदलें",
+ "@nameConflictStrategyReplace": {},
+ "themeBrightnessLight": "Light",
+ "@themeBrightnessLight": {},
+ "vaultLockTypePassword": "पासवर्ड",
+ "@vaultLockTypePassword": {},
+ "storageVolumeDescriptionFallbackNonPrimary": "एसडी कार्ड",
+ "@storageVolumeDescriptionFallbackNonPrimary": {},
+ "storageAccessDialogMessage": "अगले स्क्रीन में \"{volume}\" के {directory} का चयन करें ताकि यह ऐप इसके लिए पहुंच सके।।",
+ "@storageAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "rootDirectoryDescription": "root directory",
+ "@rootDirectoryDescription": {},
+ "otherDirectoryDescription": "“{name}” directory",
+ "@otherDirectoryDescription": {
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "example": "Pictures",
+ "description": "the name of a specific directory"
+ }
+ }
+ },
+ "missingSystemFilePickerDialogMessage": "सिस्टम फ़ाइल पिकर लापता या अक्षम है। कृपया इसे सक्षम करें और फिर से प्रयास करें।।",
+ "@missingSystemFilePickerDialogMessage": {},
+ "nameConflictDialogMultipleSourceMessage": "कुछ फ़ाइलों का नाम समान है।।",
+ "@nameConflictDialogMultipleSourceMessage": {},
+ "addShortcutDialogLabel": "शॉर्टकट लेबल",
+ "@addShortcutDialogLabel": {},
+ "newVaultWarningDialogMessage": "वॉल्ट में आइटम केवल इस ऐप के लिए व अन्य के लिए नहीं उपलब्ध हैं।\n\nयदि आप इस ऐप को अनइंस्टॉल करते हैं, या इस ऐप डेटा को साफ़ करते हैं, तो आप इन सभी आइटम को खो देंगे।।",
+ "@newVaultWarningDialogMessage": {},
+ "configureVaultDialogTitle": "वॉल्ट को कॉन्फ़िगर करना",
+ "@configureVaultDialogTitle": {},
+ "moveUndatedConfirmationDialogMessage": "आगे बढ़ने से पहले आइटम की तारीख सेव करे?",
+ "@moveUndatedConfirmationDialogMessage": {},
+ "setCoverDialogLatest": "नवीनतम आइटम",
+ "@setCoverDialogLatest": {},
+ "hideFilterConfirmationDialogMessage": "मैचिंग तस्वीरें और वीडियो आपके कलेक्शन से छिपे होंगे। आप उन्हें फिर से \"गोपनीयता\" सेटिंग्स से दिखा सकते हैं।\n\nक्या आप उन्हें छिपाना चाहते हैं?",
+ "@hideFilterConfirmationDialogMessage": {},
+ "renameEntrySetPageTitle": "नाम बदलें",
+ "@renameEntrySetPageTitle": {},
+ "authenticateToConfigureVault": "वॉल्ट को कॉन्फ़िगर करने के लिए प्रमाणीकरण करें",
+ "@authenticateToConfigureVault": {},
+ "renameAlbumDialogLabelAlreadyExistsHelper": "डायरेक्टरी पहले से मौजूद",
+ "@renameAlbumDialogLabelAlreadyExistsHelper": {}
}
diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb
index 0c3b4b82e..9bc52b35b 100644
--- a/lib/l10n/app_id.arb
+++ b/lib/l10n/app_id.arb
@@ -1374,5 +1374,11 @@
"renameProcessorHash": "Hash",
"@renameProcessorHash": {},
"chipActionShowCollection": "Tampilkan di Koleksi",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "explorerActionSelectStorageVolume": "Pilih penyimpanan",
+ "@explorerActionSelectStorageVolume": {},
+ "selectStorageVolumeDialogTitle": "Pilih Penyimpanan",
+ "@selectStorageVolumeDialogTitle": {},
+ "setHomeCustom": "Kustom",
+ "@setHomeCustom": {}
}
diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb
index a48694609..f1925fcfd 100644
--- a/lib/l10n/app_ko.arb
+++ b/lib/l10n/app_ko.arb
@@ -1384,5 +1384,11 @@
"explorerActionSelectStorageVolume": "저장공간 선택",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "저장공간",
- "@selectStorageVolumeDialogTitle": {}
+ "@selectStorageVolumeDialogTitle": {},
+ "sortByDuration": "길이",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "짧은 순",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "긴 순",
+ "@sortOrderLongestFirst": {}
}
diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb
index e42a5f496..e49349f14 100644
--- a/lib/l10n/app_nl.arb
+++ b/lib/l10n/app_nl.arb
@@ -65,11 +65,11 @@
"@sourceStateLocatingPlaces": {},
"chipActionDelete": "Verwijderen",
"@chipActionDelete": {},
- "chipActionGoToAlbumPage": "Tonen Albums",
+ "chipActionGoToAlbumPage": "In Albums tonen",
"@chipActionGoToAlbumPage": {},
- "chipActionGoToCountryPage": "Tonen in Landen",
+ "chipActionGoToCountryPage": "In Landen tonen",
"@chipActionGoToCountryPage": {},
- "chipActionGoToTagPage": "Tonen in Labels",
+ "chipActionGoToTagPage": "In Labels tonen",
"@chipActionGoToTagPage": {},
"chipActionFilterOut": "Uitfilteren",
"@chipActionFilterOut": {},
@@ -113,7 +113,7 @@
"@entryActionShare": {},
"entryActionViewSource": "Bron bekijken",
"@entryActionViewSource": {},
- "entryActionShowGeoTiffOnMap": "Tonen als map overlay",
+ "entryActionShowGeoTiffOnMap": "Als kaart-overlay tonen",
"@entryActionShowGeoTiffOnMap": {},
"entryActionConvertMotionPhotoToStillImage": "Converteren naar stilstaand beeld",
"@entryActionConvertMotionPhotoToStillImage": {},
@@ -125,7 +125,7 @@
"@entryActionOpen": {},
"entryActionSetAs": "Instellen als",
"@entryActionSetAs": {},
- "entryActionOpenMap": "Tonen in map app",
+ "entryActionOpenMap": "In Kaarten-app tonen",
"@entryActionOpenMap": {},
"entryActionRotateScreen": "Scherm roteren",
"@entryActionRotateScreen": {},
@@ -147,7 +147,7 @@
"@videoActionReplay10": {},
"videoActionSkip10": "10 seconden vooruit",
"@videoActionSkip10": {},
- "videoActionSelectStreams": "Tracks selecteren",
+ "videoActionSelectStreams": "Sporen selecteren",
"@videoActionSelectStreams": {},
"videoActionSetSpeed": "Afspeelsnelheid",
"@videoActionSetSpeed": {},
@@ -155,13 +155,13 @@
"@viewerActionSettings": {},
"slideshowActionResume": "Hervatten",
"@slideshowActionResume": {},
- "slideshowActionShowInCollection": "Tonen in Collectie",
+ "slideshowActionShowInCollection": "In Collectie tonen",
"@slideshowActionShowInCollection": {},
- "entryInfoActionEditDate": "Bewerk datum & tijd",
+ "entryInfoActionEditDate": "Datum & tijd bewerken",
"@entryInfoActionEditDate": {},
- "entryInfoActionEditLocation": "Bewerk locatie",
+ "entryInfoActionEditLocation": "Locatie bewerken",
"@entryInfoActionEditLocation": {},
- "entryInfoActionEditTitleDescription": "Wijzig titel & omschrijving",
+ "entryInfoActionEditTitleDescription": "Titel & beschrijving bewerken",
"@entryInfoActionEditTitleDescription": {},
"entryInfoActionEditRating": "Waardering bewerken",
"@entryInfoActionEditRating": {},
@@ -221,11 +221,11 @@
"@coordinateDmsWest": {},
"unitSystemMetric": "Metrisch",
"@unitSystemMetric": {},
- "unitSystemImperial": "Imperiaal",
+ "unitSystemImperial": "Brits-Amerikaans",
"@unitSystemImperial": {},
"videoLoopModeNever": "Nooit",
"@videoLoopModeNever": {},
- "videoLoopModeShortOnly": "Enkel korte videos",
+ "videoLoopModeShortOnly": "Alleen korte video's",
"@videoLoopModeShortOnly": {},
"videoLoopModeAlways": "Altijd",
"@videoLoopModeAlways": {},
@@ -233,7 +233,7 @@
"@videoControlsPlay": {},
"videoControlsPlaySeek": "Speel & zoek terug/vooruit",
"@videoControlsPlaySeek": {},
- "videoControlsPlayOutside": "Openen met andere speler",
+ "videoControlsPlayOutside": "Met andere speler openen",
"@videoControlsPlayOutside": {},
"videoControlsNone": "Geen",
"@videoControlsNone": {},
@@ -255,13 +255,13 @@
"@nameConflictStrategySkip": {},
"keepScreenOnNever": "Nooit",
"@keepScreenOnNever": {},
- "keepScreenOnViewerOnly": "Enkel Viewer pagina",
+ "keepScreenOnViewerOnly": "Alleen Viewerpagina",
"@keepScreenOnViewerOnly": {},
"keepScreenOnAlways": "Altijd",
"@keepScreenOnAlways": {},
- "accessibilityAnimationsRemove": "Scherm effecten uitschakelen",
+ "accessibilityAnimationsRemove": "Schermeffecten uitschakelen",
"@accessibilityAnimationsRemove": {},
- "accessibilityAnimationsKeep": "Scherm effecten houden",
+ "accessibilityAnimationsKeep": "Schermeffecten behouden",
"@accessibilityAnimationsKeep": {},
"displayRefreshRatePreferHighest": "Hoogste waardering",
"@displayRefreshRatePreferHighest": {},
@@ -269,7 +269,7 @@
"@displayRefreshRatePreferLowest": {},
"videoPlaybackSkip": "Overslaan",
"@videoPlaybackSkip": {},
- "videoPlaybackMuted": "Gedempte afspelen",
+ "videoPlaybackMuted": "Gedempt afspelen",
"@videoPlaybackMuted": {},
"videoPlaybackWithSound": "Met geluid afspelen",
"@videoPlaybackWithSound": {},
@@ -289,13 +289,13 @@
"@viewerTransitionZoomIn": {},
"viewerTransitionNone": "Geen",
"@viewerTransitionNone": {},
- "wallpaperTargetHome": "Home scherm",
+ "wallpaperTargetHome": "Startscherm",
"@wallpaperTargetHome": {},
- "wallpaperTargetLock": "Vergrendel scherm",
+ "wallpaperTargetLock": "Vergrendelingsscherm",
"@wallpaperTargetLock": {},
- "wallpaperTargetHomeLock": "Home and Vergrendel schermen",
+ "wallpaperTargetHomeLock": "Start- en vergrendelingsschermen",
"@wallpaperTargetHomeLock": {},
- "widgetOpenPageHome": "Open startscherm",
+ "widgetOpenPageHome": "Startscherm openen",
"@widgetOpenPageHome": {},
"albumTierNew": "Nieuw",
"@albumTierNew": {},
@@ -315,7 +315,7 @@
"@rootDirectoryDescription": {},
"otherDirectoryDescription": "“{name}” map",
"@otherDirectoryDescription": {},
- "storageAccessDialogMessage": "Selecteer de {directory} van “{volume}”, in het volgende scherm om deze app er toegang toe te geven.",
+ "storageAccessDialogMessage": "Selecteer in het volgende scherm de {directory} van “{volume}” om deze app er toegang toe te geven.",
"@storageAccessDialogMessage": {},
"restrictedAccessDialogMessage": "Deze applicatie mag geen bestanden wijzigen in de {directory} van “{volume}”,.\n\n Gebruik een vooraf geïnstalleerde filemanager of galerij-app om de items naar een andere map te verplaatsen.",
"@restrictedAccessDialogMessage": {},
@@ -335,17 +335,17 @@
"@addShortcutButtonLabel": {},
"noMatchingAppDialogMessage": "Er zijn geen apps die dit ondersteunen.",
"@noMatchingAppDialogMessage": {},
- "binEntriesConfirmationDialogMessage": "{count, plural, =1{Dit item naar de prullenbak verplaatsen??} other{Verplaats deze {count} items naar de prullenbak?}}",
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{Dit item naar de prullenbak verplaatsen??} other{Deze {count} items naar de prullenbak verplaatsen?}}",
"@binEntriesConfirmationDialogMessage": {},
- "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Verwijder dit item?} other{Verwijder deze {count} items?}}",
+ "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Dit item verwijderen?} other{Deze {count} items verwijderen?}}",
"@deleteEntriesConfirmationDialogMessage": {},
- "moveUndatedConfirmationDialogMessage": "Datums opslaan voordat u doorgaat??",
+ "moveUndatedConfirmationDialogMessage": "Datums opslaan alvorens door te gaan?",
"@moveUndatedConfirmationDialogMessage": {},
"moveUndatedConfirmationDialogSetDate": "Datums opslaan",
"@moveUndatedConfirmationDialogSetDate": {},
- "videoResumeDialogMessage": "Wil je het afspelen hervatten op {time}?",
+ "videoResumeDialogMessage": "Afspelen hervatten om {time}?",
"@videoResumeDialogMessage": {},
- "videoStartOverButtonLabel": "OPNIEUW BEGINNEN",
+ "videoStartOverButtonLabel": "OPNIEUW AFSPELLEN",
"@videoStartOverButtonLabel": {},
"videoResumeButtonLabel": "HERVATTEN",
"@videoResumeButtonLabel": {},
@@ -355,7 +355,7 @@
"@setCoverDialogAuto": {},
"setCoverDialogCustom": "Aangepast",
"@setCoverDialogCustom": {},
- "hideFilterConfirmationDialogMessage": "Overeenkomende foto’s en video’s worden verborgen binnen jouw verzameling. Je kunt ze opnieuw weergeven via de “Privacy”-instellingen.\n\nWeet je zeker dat je ze wilt verbergen?",
+ "hideFilterConfirmationDialogMessage": "Overeenkomstige foto’s en video’s worden verborgen binnen jouw verzameling. Je kunt ze opnieuw weergeven via de “Privacy”-instellingen.\n\nWeet je zeker dat je ze wilt verbergen?",
"@hideFilterConfirmationDialogMessage": {},
"newAlbumDialogTitle": "Nieuw Album",
"@newAlbumDialogTitle": {},
@@ -381,11 +381,11 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Naam",
"@renameProcessorName": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Verwijder dit album en het item binnen dit album?} other{Verwijder dit album en de {count} items binnen dit album?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Dit album en het item erbinnen verwijderen?} other{Dit album en de {count} items erbinnen verwijderen?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Verwijder deze albums en het item binnen deze albums?} other{Verwijder deze albums en de {count} items binnen deze albums?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Deze albums en de items erbinnen verwijderen?} other{Deze albums en de {count} items erbinnen verwijderen?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {},
- "exportEntryDialogFormat": "Formaat:",
+ "exportEntryDialogFormat": "Type:",
"@exportEntryDialogFormat": {},
"exportEntryDialogWidth": "Breedte",
"@exportEntryDialogWidth": {},
@@ -397,11 +397,11 @@
"@editEntryDialogTargetFieldsHeader": {},
"editEntryDateDialogTitle": "Datum & Tijd",
"@editEntryDateDialogTitle": {},
- "editEntryDateDialogSetCustom": "Stel een custom datum in",
+ "editEntryDateDialogSetCustom": "Aangepaste datum instellen",
"@editEntryDateDialogSetCustom": {},
- "editEntryDateDialogCopyField": "Kopiëren van andere datum",
+ "editEntryDateDialogCopyField": "Van andere datum kopiëren",
"@editEntryDateDialogCopyField": {},
- "editEntryDialogCopyFromItem": "Kopiëren van ander item",
+ "editEntryDialogCopyFromItem": "Van ander item kopiëren",
"@editEntryDialogCopyFromItem": {},
"editEntryDateDialogExtractFromTitle": "Uit titel halen",
"@editEntryDateDialogExtractFromTitle": {},
@@ -415,7 +415,7 @@
"@durationDialogMinutes": {},
"editEntryLocationDialogTitle": "Locatie",
"@editEntryLocationDialogTitle": {},
- "editEntryLocationDialogChooseOnMap": "Kies op kaart",
+ "editEntryLocationDialogChooseOnMap": "Op kaart kiezen",
"@editEntryLocationDialogChooseOnMap": {},
"editEntryLocationDialogLatitude": "Breedtegraad",
"@editEntryLocationDialogLatitude": {},
@@ -443,9 +443,9 @@
"@videoStreamSelectionDialogText": {},
"videoStreamSelectionDialogOff": "Uit",
"@videoStreamSelectionDialogOff": {},
- "videoStreamSelectionDialogTrack": "Nummer",
+ "videoStreamSelectionDialogTrack": "Spoor",
"@videoStreamSelectionDialogTrack": {},
- "videoStreamSelectionDialogNoSelection": "Er zijn geen andere nummers.",
+ "videoStreamSelectionDialogNoSelection": "Er zijn geen andere sporen.",
"@videoStreamSelectionDialogNoSelection": {},
"genericSuccessFeedback": "Klaar!",
"@genericSuccessFeedback": {},
@@ -453,9 +453,9 @@
"@genericFailureFeedback": {},
"menuActionConfigureView": "Beeld",
"@menuActionConfigureView": {},
- "menuActionSelect": "Selecteer",
+ "menuActionSelect": "Selecteren",
"@menuActionSelect": {},
- "menuActionSelectAll": "Selecteer alles",
+ "menuActionSelectAll": "Alles selecteren",
"@menuActionSelectAll": {},
"menuActionSelectNone": "Selectie ongedaan maken",
"@menuActionSelectNone": {},
@@ -493,17 +493,17 @@
"@aboutPageTitle": {},
"aboutLinkLicense": "Licentie",
"@aboutLinkLicense": {},
- "aboutBugSectionTitle": "Bug Reporteren",
+ "aboutBugSectionTitle": "Foutmelding",
"@aboutBugSectionTitle": {},
"aboutBugSaveLogInstruction": "Sla applicatielogs op in een bestand",
"@aboutBugSaveLogInstruction": {},
- "aboutBugCopyInfoInstruction": "Kopieer systeem informatie",
+ "aboutBugCopyInfoInstruction": "Systeeminformatie kopiëren",
"@aboutBugCopyInfoInstruction": {},
"aboutBugCopyInfoButton": "Kopieer",
"@aboutBugCopyInfoButton": {},
- "aboutBugReportInstruction": "Reporteer op GitHub met de logs en systeeminformatie",
+ "aboutBugReportInstruction": "Melden op GitHub met de logs en systeeminformatie",
"@aboutBugReportInstruction": {},
- "aboutBugReportButton": "Reporteer",
+ "aboutBugReportButton": "Melden",
"@aboutBugReportButton": {},
"aboutCreditsSectionTitle": "Dankbetuiging",
"@aboutCreditsSectionTitle": {},
@@ -531,9 +531,9 @@
"@collectionPageTitle": {},
"collectionPickPageTitle": "Kies",
"@collectionPickPageTitle": {},
- "collectionSelectPageTitle": "Selecteer items",
+ "collectionSelectPageTitle": "Items selecteren",
"@collectionSelectPageTitle": {},
- "collectionActionShowTitleSearch": "Laat titel filter zien",
+ "collectionActionShowTitleSearch": "Titelfilter weergeven",
"@collectionActionShowTitleSearch": {},
"collectionActionHideTitleSearch": "Verberg titel filter",
"@collectionActionHideTitleSearch": {},
@@ -547,7 +547,7 @@
"@collectionActionMove": {},
"collectionActionRescan": "Opnieuw indexeren",
"@collectionActionRescan": {},
- "collectionActionEdit": "Wijzigen",
+ "collectionActionEdit": "Bewerken",
"@collectionActionEdit": {},
"collectionSearchTitlesHintText": "Zoek op titel",
"@collectionSearchTitlesHintText": {},
@@ -575,7 +575,7 @@
"@collectionMoveFailureFeedback": {},
"collectionRenameFailureFeedback": "{count, plural, =1{Kan 1 item niet hernoemen} other{Kan {count} items niet hernoemen}}",
"@collectionRenameFailureFeedback": {},
- "collectionEditFailureFeedback": "{count, plural, =1{Kan 1 item niet wijzigen} other{Kan {count} items niet wijzigen}}",
+ "collectionEditFailureFeedback": "{count, plural, =1{Kan 1 item niet bewerken} other{Kan {count} items niet bewerken}}",
"@collectionEditFailureFeedback": {},
"collectionExportFailureFeedback": "{count, plural, =1{Kan 1 pagina niet exporteren} other{Kan {count} pagina’s niet exporteren}}",
"@collectionExportFailureFeedback": {},
@@ -585,7 +585,7 @@
"@collectionMoveSuccessFeedback": {},
"collectionRenameSuccessFeedback": "{count, plural, =1{1 item hernoemd} other{{count} items hernoemd}}",
"@collectionRenameSuccessFeedback": {},
- "collectionEditSuccessFeedback": "{count, plural, =1{1 item gewijzigd} other{{count} items gewijzigd}}",
+ "collectionEditSuccessFeedback": "{count, plural, =1{1 item bewerkt} other{{count} items bewerkt}}",
"@collectionEditSuccessFeedback": {},
"collectionEmptyFavourites": "Geen favourieten",
"@collectionEmptyFavourites": {},
@@ -595,9 +595,9 @@
"@collectionEmptyImages": {},
"collectionEmptyGrantAccessButtonLabel": "Toegang verlenen",
"@collectionEmptyGrantAccessButtonLabel": {},
- "collectionSelectSectionTooltip": "Selecteer sectie",
+ "collectionSelectSectionTooltip": "Sectie selecteren",
"@collectionSelectSectionTooltip": {},
- "collectionDeselectSectionTooltip": "Deselecteer sectie",
+ "collectionDeselectSectionTooltip": "Sectie niet selecteren",
"@collectionDeselectSectionTooltip": {},
"drawerAboutButton": "Over",
"@drawerAboutButton": {},
@@ -677,7 +677,7 @@
"@albumCamera": {},
"albumDownload": "Opslaan",
"@albumDownload": {},
- "albumScreenshots": "Schermafbeeldingen",
+ "albumScreenshots": "Schermopnames",
"@albumScreenshots": {},
"albumScreenRecordings": "Schermopnames",
"@albumScreenRecordings": {},
@@ -751,25 +751,25 @@
"@settingsHomeTile": {},
"settingsHomeDialogTitle": "Startscherm",
"@settingsHomeDialogTitle": {},
- "settingsShowBottomNavigationBar": "Laat onderste navigatiebalk zien",
+ "settingsShowBottomNavigationBar": "Onderste navigatiebalk weergeven",
"@settingsShowBottomNavigationBar": {},
- "settingsKeepScreenOnTile": "Houd het scherm aan",
+ "settingsKeepScreenOnTile": "Scherm aan houden",
"@settingsKeepScreenOnTile": {},
- "settingsKeepScreenOnDialogTitle": "Houd het scherm aan",
+ "settingsKeepScreenOnDialogTitle": "Scherm aan houden",
"@settingsKeepScreenOnDialogTitle": {},
- "settingsDoubleBackExit": "Tik twee keer op “terug” om af te sluiten",
+ "settingsDoubleBackExit": "Twee keer op “terug” tikken om af te sluiten",
"@settingsDoubleBackExit": {},
- "settingsConfirmationTile": "Bevestigingsscherm",
+ "settingsConfirmationTile": "Bevestigingsdialogen",
"@settingsConfirmationTile": {},
- "settingsConfirmationDialogTitle": "Bevestigingsschermen",
+ "settingsConfirmationDialogTitle": "Bevestigingsdialogen",
"@settingsConfirmationDialogTitle": {},
- "settingsConfirmationBeforeDeleteItems": "Bevestig voordat je items voor altijd verwijdert",
+ "settingsConfirmationBeforeDeleteItems": "Bevestiging vragen voordat items voor altijd worden verwijderd",
"@settingsConfirmationBeforeDeleteItems": {},
- "settingsConfirmationBeforeMoveToBinItems": "Bevestig voordat u items naar de prullenbak verplaatst",
+ "settingsConfirmationBeforeMoveToBinItems": "Bevestiging vragen voordat items naar de prullenbak worden verplaatst",
"@settingsConfirmationBeforeMoveToBinItems": {},
- "settingsConfirmationBeforeMoveUndatedItems": "Bevestigvoordat u ongedateerde items verplaatst",
+ "settingsConfirmationBeforeMoveUndatedItems": "Bevestiging vragen voordat ongedateerde items worden verplaatst",
"@settingsConfirmationBeforeMoveUndatedItems": {},
- "settingsConfirmationAfterMoveToBinItems": "Toon bevestigingsbericht na het verplaatsen van items naar de prullenbak",
+ "settingsConfirmationAfterMoveToBinItems": "Bevestigingsbericht weergeven na het verplaatsen van items naar de prullenbak",
"@settingsConfirmationAfterMoveToBinItems": {},
"settingsNavigationDrawerTile": "Navigatiemenu",
"@settingsNavigationDrawerTile": {},
@@ -791,35 +791,35 @@
"@settingsThumbnailOverlayTile": {},
"settingsThumbnailOverlayPageTitle": "Overlay",
"@settingsThumbnailOverlayPageTitle": {},
- "settingsThumbnailShowFavouriteIcon": "Favorieten icoon zichtbaar",
+ "settingsThumbnailShowFavouriteIcon": "Favorieten-pictogram tonen",
"@settingsThumbnailShowFavouriteIcon": {},
"settingsThumbnailShowTagIcon": "Label-pictogram tonen",
"@settingsThumbnailShowTagIcon": {},
- "settingsThumbnailShowLocationIcon": "Locatie icoon zichtbaar",
+ "settingsThumbnailShowLocationIcon": "Locatie-pictogram tonen",
"@settingsThumbnailShowLocationIcon": {},
- "settingsThumbnailShowMotionPhotoIcon": "Bewegende foto icoon zichtbaar",
+ "settingsThumbnailShowMotionPhotoIcon": "Bewegende foto-pictogram tonen",
"@settingsThumbnailShowMotionPhotoIcon": {},
"settingsThumbnailShowRating": "Waardering tonen",
"@settingsThumbnailShowRating": {},
- "settingsThumbnailShowRawIcon": "RAW icoon zichtbaar",
+ "settingsThumbnailShowRawIcon": "RAW-pictogram tonen",
"@settingsThumbnailShowRawIcon": {},
- "settingsThumbnailShowVideoDuration": "Videoduur zichtbaar",
+ "settingsThumbnailShowVideoDuration": "Videoduur tonen",
"@settingsThumbnailShowVideoDuration": {},
"settingsCollectionQuickActionsTile": "Snelle bewerkingen",
"@settingsCollectionQuickActionsTile": {},
- "settingsCollectionQuickActionEditorPageTitle": "Snelle bewerkingen",
+ "settingsCollectionQuickActionEditorPageTitle": "Snelle acties",
"@settingsCollectionQuickActionEditorPageTitle": {},
"settingsCollectionQuickActionTabBrowsing": "Blader",
"@settingsCollectionQuickActionTabBrowsing": {},
"settingsCollectionQuickActionTabSelecting": "Selecteren",
"@settingsCollectionQuickActionTabSelecting": {},
- "settingsCollectionBrowsingQuickActionEditorBanner": "Houd ingedrukt om knoppen te verplaatsen en te selecteren welke acties worden weergegeven bij het bladeren door items.",
+ "settingsCollectionBrowsingQuickActionEditorBanner": "Houd knoppen ingedrukt om deze te verplaatsen en te selecteren welke acties worden weergegeven bij het bladeren door items.",
"@settingsCollectionBrowsingQuickActionEditorBanner": {},
- "settingsCollectionSelectionQuickActionEditorBanner": "Houd ingedrukt om knoppen te verplaatsen en te selecteren welke acties worden weergegeven bij het selecteren van items.",
+ "settingsCollectionSelectionQuickActionEditorBanner": "Houd knoppen ingedrukt om deze te verplaatsen en te selecteren welke acties worden weergegeven bij het selecteren van items.",
"@settingsCollectionSelectionQuickActionEditorBanner": {},
"settingsViewerSectionTitle": "Voorbeeld",
"@settingsViewerSectionTitle": {},
- "settingsViewerGestureSideTapNext": "Druk op het scherm om het vorige/volgende item weer te geven",
+ "settingsViewerGestureSideTapNext": "Tik op het scherm om het vorige/volgende item weer te geven",
"@settingsViewerGestureSideTapNext": {},
"settingsViewerUseCutout": "Uitgesneden gebied gebruiken",
"@settingsViewerUseCutout": {},
@@ -831,9 +831,9 @@
"@settingsImageBackground": {},
"settingsViewerQuickActionsTile": "Snelle bewerkingen",
"@settingsViewerQuickActionsTile": {},
- "settingsViewerQuickActionEditorPageTitle": "Snelle bewerkingen",
+ "settingsViewerQuickActionEditorPageTitle": "Snelle acties",
"@settingsViewerQuickActionEditorPageTitle": {},
- "settingsViewerQuickActionEditorBanner": "Houd ingedrukt om knoppen te verplaatsen en te selecteren welke acties in de viewer worden weergegeven.",
+ "settingsViewerQuickActionEditorBanner": "Houd knoppen ingedrukt om deze te verplaatsen en te selecteren welke acties in de viewer worden weergegeven.",
"@settingsViewerQuickActionEditorBanner": {},
"settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Zichtbare knoppen",
"@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
@@ -845,17 +845,17 @@
"@settingsViewerOverlayTile": {},
"settingsViewerOverlayPageTitle": "Overlay",
"@settingsViewerOverlayPageTitle": {},
- "settingsViewerShowOverlayOnOpening": "Zichtbaar bij openen",
+ "settingsViewerShowOverlayOnOpening": "Bij openen weergeven",
"@settingsViewerShowOverlayOnOpening": {},
- "settingsViewerShowMinimap": "Laat kleine kaart zien",
+ "settingsViewerShowMinimap": "Kleine kaart tonen",
"@settingsViewerShowMinimap": {},
- "settingsViewerShowInformation": "Laat informatie zien",
+ "settingsViewerShowInformation": "Informatie tonen",
"@settingsViewerShowInformation": {},
- "settingsViewerShowInformationSubtitle": "Laat titel, datum, locatie, etc zien.",
+ "settingsViewerShowInformationSubtitle": "Titel, datum, locatie, etc.",
"@settingsViewerShowInformationSubtitle": {},
- "settingsViewerShowShootingDetails": "Laat opnamedetails zien",
+ "settingsViewerShowShootingDetails": "Opnamedetails tonen",
"@settingsViewerShowShootingDetails": {},
- "settingsViewerShowOverlayThumbnails": "Laat miniaturen zien",
+ "settingsViewerShowOverlayThumbnails": "Miniaturen tonen",
"@settingsViewerShowOverlayThumbnails": {},
"settingsViewerEnableOverlayBlurEffect": "Vervagingseffect",
"@settingsViewerEnableOverlayBlurEffect": {},
@@ -883,9 +883,9 @@
"@settingsVideoPageTitle": {},
"settingsVideoSectionTitle": "Video",
"@settingsVideoSectionTitle": {},
- "settingsVideoShowVideos": "Videos",
+ "settingsVideoShowVideos": "Video's weergeven",
"@settingsVideoShowVideos": {},
- "settingsVideoEnableHardwareAcceleration": "Hardware acceleratie",
+ "settingsVideoEnableHardwareAcceleration": "Hardware-versnelling",
"@settingsVideoEnableHardwareAcceleration": {},
"settingsVideoAutoPlay": "Automatisch afspelen",
"@settingsVideoAutoPlay": {},
@@ -905,7 +905,7 @@
"@settingsSubtitleThemeTextAlignmentDialogTitle": {},
"settingsSubtitleThemeTextSize": "Tekstgroote",
"@settingsSubtitleThemeTextSize": {},
- "settingsSubtitleThemeShowOutline": "Laat omtrek en schaduw zien",
+ "settingsSubtitleThemeShowOutline": "Omtrek en schaduw tonen",
"@settingsSubtitleThemeShowOutline": {},
"settingsSubtitleThemeTextColor": "Tekstkleur",
"@settingsSubtitleThemeTextColor": {},
@@ -937,9 +937,9 @@
"@settingsAllowInstalledAppAccess": {},
"settingsAllowInstalledAppAccessSubtitle": "Gebruikt om de albumweergave te verbeteren",
"@settingsAllowInstalledAppAccessSubtitle": {},
- "settingsAllowErrorReporting": "Anonieme foutrapportage toestaan",
+ "settingsAllowErrorReporting": "Anonieme foutmeldingen toestaan",
"@settingsAllowErrorReporting": {},
- "settingsSaveSearchHistory": "Bewaar zoekgeschiedenis",
+ "settingsSaveSearchHistory": "Zoekgeschiedenis opslaan",
"@settingsSaveSearchHistory": {},
"settingsEnableBin": "Prullenbak gebruiken",
"@settingsEnableBin": {},
@@ -957,7 +957,7 @@
"@settingsHiddenFiltersEmpty": {},
"settingsHiddenItemsTabPaths": "Verborgen paden",
"@settingsHiddenItemsTabPaths": {},
- "settingsHiddenPathsBanner": "Foto’s en video’s in deze mappen, of een van hun submappen, verschijnen niet in je verzameling.",
+ "settingsHiddenPathsBanner": "Foto’s en video’s in deze mappen en onderliggende mappen, verschijnen niet in je verzameling.",
"@settingsHiddenPathsBanner": {},
"addPathTooltip": "Pad toevoegen",
"@addPathTooltip": {},
@@ -977,7 +977,7 @@
"@settingsRemoveAnimationsTile": {},
"settingsRemoveAnimationsDialogTitle": "Animaties verwijderen",
"@settingsRemoveAnimationsDialogTitle": {},
- "settingsTimeToTakeActionTile": "Tijd om actie te ondernemen",
+ "settingsTimeToTakeActionTile": "Reactietijd",
"@settingsTimeToTakeActionTile": {},
"settingsDisplaySectionTitle": "Scherm",
"@settingsDisplaySectionTitle": {},
@@ -985,13 +985,13 @@
"@settingsThemeBrightnessTile": {},
"settingsThemeBrightnessDialogTitle": "Thema",
"@settingsThemeBrightnessDialogTitle": {},
- "settingsThemeColorHighlights": "Kleur highlights",
+ "settingsThemeColorHighlights": "Kleurmarkeringen",
"@settingsThemeColorHighlights": {},
- "settingsThemeEnableDynamicColor": "Dynamische kleur",
+ "settingsThemeEnableDynamicColor": "Dynamische kleuren",
"@settingsThemeEnableDynamicColor": {},
- "settingsDisplayRefreshRateModeTile": "Vernieuwingsfrequentie weergeven",
+ "settingsDisplayRefreshRateModeTile": "Vernieuwingssnelheid weergeven",
"@settingsDisplayRefreshRateModeTile": {},
- "settingsDisplayRefreshRateModeDialogTitle": "Vernieuwingsfrequentie",
+ "settingsDisplayRefreshRateModeDialogTitle": "Vernieuwingssnelheid",
"@settingsDisplayRefreshRateModeDialogTitle": {},
"settingsLanguageSectionTitle": "Taal & landinstellingen",
"@settingsLanguageSectionTitle": {},
@@ -999,9 +999,9 @@
"@settingsLanguageTile": {},
"settingsLanguagePageTitle": "Taal",
"@settingsLanguagePageTitle": {},
- "settingsCoordinateFormatTile": "Coördinaten format",
+ "settingsCoordinateFormatTile": "Coördinaten-weergave",
"@settingsCoordinateFormatTile": {},
- "settingsCoordinateFormatDialogTitle": "Coördinaten format",
+ "settingsCoordinateFormatDialogTitle": "Coördinaten-weergave",
"@settingsCoordinateFormatDialogTitle": {},
"settingsUnitSystemTile": "Eenheden",
"@settingsUnitSystemTile": {},
@@ -1013,7 +1013,7 @@
"@settingsWidgetPageTitle": {},
"settingsWidgetShowOutline": "Contour",
"@settingsWidgetShowOutline": {},
- "settingsWidgetOpenPage": "Wanneer u op de widget tikt",
+ "settingsWidgetOpenPage": "Bij het tikken op de widget",
"@settingsWidgetOpenPage": {},
"settingsCollectionTile": "Verzameling",
"@settingsCollectionTile": {},
@@ -1067,7 +1067,7 @@
"@viewerInfoLabelAddress": {},
"mapStyleDialogTitle": "Kaartstijl",
"@mapStyleDialogTitle": {},
- "mapStyleTooltip": "Selecteer kaart stijl",
+ "mapStyleTooltip": "Kaartstijl selecteren",
"@mapStyleTooltip": {},
"mapZoomInTooltip": "Inzoomen",
"@mapZoomInTooltip": {},
@@ -1079,13 +1079,13 @@
"@mapAttributionOsmHot": {},
"mapAttributionStamen": "Kaartgegevens © [OpenStreetMap](https://www.openstreetmap.org/copyright) bijdragers • Tegels door [Stamen Design](https://stamen.com), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0)",
"@mapAttributionStamen": {},
- "openMapPageTooltip": "Bekijk op kaartpagina",
+ "openMapPageTooltip": "Op kaartpagina tonen",
"@openMapPageTooltip": {},
- "mapEmptyRegion": "Geen afbeeldingen in de geselecteerde regio",
+ "mapEmptyRegion": "Geen afbeeldingen in dit gebied",
"@mapEmptyRegion": {},
"viewerInfoOpenEmbeddedFailureFeedback": "Kan ingesloten gegevens niet extraheren",
"@viewerInfoOpenEmbeddedFailureFeedback": {},
- "viewerInfoOpenLinkText": "Open",
+ "viewerInfoOpenLinkText": "Openen",
"@viewerInfoOpenLinkText": {},
"viewerInfoViewXmlLinkText": "Bekijk XML",
"@viewerInfoViewXmlLinkText": {},
@@ -1119,19 +1119,19 @@
"@panoramaDisableSensorControl": {},
"sourceViewerPageTitle": "Source",
"@sourceViewerPageTitle": {},
- "filePickerShowHiddenFiles": "Verborgen bestanden laten zien",
+ "filePickerShowHiddenFiles": "Verborgen bestanden weergeven",
"@filePickerShowHiddenFiles": {},
- "filePickerDoNotShowHiddenFiles": "Verborgen bestanden niet laten zien",
+ "filePickerDoNotShowHiddenFiles": "Verborgen bestanden niet tonen",
"@filePickerDoNotShowHiddenFiles": {},
- "filePickerOpenFrom": "Openen met",
+ "filePickerOpenFrom": "Openen van",
"@filePickerOpenFrom": {},
"filePickerNoItems": "Geen items",
"@filePickerNoItems": {},
"filePickerUseThisFolder": "Deze map gebruiken",
"@filePickerUseThisFolder": {},
- "widgetOpenPageCollection": "Open verzameling",
+ "widgetOpenPageCollection": "Verzameling openen",
"@widgetOpenPageCollection": {},
- "widgetOpenPageViewer": "Open voorbeeld",
+ "widgetOpenPageViewer": "Voorbeeld openen",
"@widgetOpenPageViewer": {},
"durationDialogSeconds": "Seconden",
"@durationDialogSeconds": {},
@@ -1167,9 +1167,9 @@
"@filterNoAddressLabel": {},
"filterAspectRatioPortraitLabel": "Staand",
"@filterAspectRatioPortraitLabel": {},
- "widgetDisplayedItemRandom": "Willekeurige",
+ "widgetDisplayedItemRandom": "Willekeurig",
"@widgetDisplayedItemRandom": {},
- "widgetDisplayedItemMostRecent": "Meest recente",
+ "widgetDisplayedItemMostRecent": "Laatst gebruikt",
"@widgetDisplayedItemMostRecent": {},
"keepScreenOnVideoPlayback": "Tijdens het afspelen van video",
"@keepScreenOnVideoPlayback": {},
@@ -1191,7 +1191,7 @@
"@stopTooltip": {},
"chipActionLock": "Vergrendel",
"@chipActionLock": {},
- "chipActionShowCountryStates": "Status tonen",
+ "chipActionShowCountryStates": "Staten tonen",
"@chipActionShowCountryStates": {},
"chipActionGoToPlacePage": "In Plaatsen tonen",
"@chipActionGoToPlacePage": {},
@@ -1199,15 +1199,15 @@
"@subtitlePositionTop": {},
"subtitlePositionBottom": "Onder",
"@subtitlePositionBottom": {},
- "settingsThumbnailShowHdrIcon": "HDR icoon zichtbaar",
+ "settingsThumbnailShowHdrIcon": "HDR-pictogram tonen",
"@settingsThumbnailShowHdrIcon": {},
"editorTransformCrop": "Bijsnijden",
"@editorTransformCrop": {},
- "patternDialogConfirm": "Bevestig patroon",
+ "patternDialogConfirm": "Patroon bevestigen",
"@patternDialogConfirm": {},
"pinDialogEnter": "Voer PIN in",
"@pinDialogEnter": {},
- "settingsAskEverytime": "Vraag elke keer",
+ "settingsAskEverytime": "Elke keer vragen",
"@settingsAskEverytime": {},
"aboutDataUsageExternal": "Extern",
"@aboutDataUsageExternal": {},
@@ -1217,7 +1217,7 @@
"@maxBrightnessAlways": {},
"patternDialogEnter": "Voer patroon in",
"@patternDialogEnter": {},
- "settingsViewerShowDescription": "Laat beschrijving zien",
+ "settingsViewerShowDescription": "Beschrijving tonen",
"@settingsViewerShowDescription": {},
"exportEntryDialogQuality": "Kwaliteit",
"@exportEntryDialogQuality": {},
@@ -1241,13 +1241,13 @@
"@overlayHistogramLuminance": {},
"videoResumptionModeNever": "Nooit",
"@videoResumptionModeNever": {},
- "pinDialogConfirm": "Bevestig PIN",
+ "pinDialogConfirm": "PIN bevestigen",
"@pinDialogConfirm": {},
"passwordDialogEnter": "Voer wachtwoord in",
"@passwordDialogEnter": {},
- "passwordDialogConfirm": "Bevestig wachtwoord",
+ "passwordDialogConfirm": "Wachtwoord bevestigen",
"@passwordDialogConfirm": {},
- "settingsViewerShowHistogram": "Laat histogram zien",
+ "settingsViewerShowHistogram": "Histogram weergeven",
"@settingsViewerShowHistogram": {},
"settingsVideoGestureVerticalDragBrightnessVolume": "Veeg omhoog of naar beneden om helderheid/volume aan te passen",
"@settingsVideoGestureVerticalDragBrightnessVolume": {},
@@ -1271,7 +1271,7 @@
"@videoResumptionModeAlways": {},
"exportEntryDialogWriteMetadata": "Metadata schrijven",
"@exportEntryDialogWriteMetadata": {},
- "chipActionShowCollection": "Tonen in Collectie",
+ "chipActionShowCollection": "In Collectie tonen",
"@chipActionShowCollection": {},
"entryActionCast": "Casten",
"@entryActionCast": {},
@@ -1329,7 +1329,7 @@
"@settingsVideoBackgroundMode": {},
"configureVaultDialogTitle": "Kluis configureren",
"@configureVaultDialogTitle": {},
- "settingsWidgetDisplayedItem": "Getoond item",
+ "settingsWidgetDisplayedItem": "Zichtbaar item",
"@settingsWidgetDisplayedItem": {},
"albumTierVaults": "Kluizen",
"@albumTierVaults": {},
@@ -1367,7 +1367,7 @@
"@widgetTapUpdateWidget": {},
"authenticateToConfigureVault": "Verifieer om de kluis te configureren",
"@authenticateToConfigureVault": {},
- "settingsConfirmationVaultDataLoss": "Waarschuwing voor verlies van kluisgegevens weergeven",
+ "settingsConfirmationVaultDataLoss": "Waarschuwing weergeven voor verlies van kluisgegevens",
"@settingsConfirmationVaultDataLoss": {},
"newVaultDialogTitle": "Nieuwe kluis",
"@newVaultDialogTitle": {},
@@ -1383,8 +1383,14 @@
"@collectionActionSetHome": {},
"setHomeCustom": "Aangepast",
"@setHomeCustom": {},
- "explorerActionSelectStorageVolume": "Selecteer opslag",
+ "explorerActionSelectStorageVolume": "Opslag selecteren",
"@explorerActionSelectStorageVolume": {},
- "selectStorageVolumeDialogTitle": "Selecteer opslag",
- "@selectStorageVolumeDialogTitle": {}
+ "selectStorageVolumeDialogTitle": "Opslag selecteren",
+ "@selectStorageVolumeDialogTitle": {},
+ "sortByDuration": "Op lengte",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "Kortste eerst",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "Langste eerst",
+ "@sortOrderLongestFirst": {}
}
diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb
index 4dab574a9..60a9c651c 100644
--- a/lib/l10n/app_pl.arb
+++ b/lib/l10n/app_pl.arb
@@ -1536,5 +1536,17 @@
"chipActionGoToExplorerPage": "Pokaż w przeglądarce",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Przeglądarka",
- "@explorerPageTitle": {}
+ "@explorerPageTitle": {},
+ "selectStorageVolumeDialogTitle": "Wybierz pamięć",
+ "@selectStorageVolumeDialogTitle": {},
+ "explorerActionSelectStorageVolume": "Wybierz pamięć",
+ "@explorerActionSelectStorageVolume": {},
+ "setHomeCustom": "Własny",
+ "@setHomeCustom": {},
+ "sortByDuration": "Według czasu trwania",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "Najkrótsze najpierw",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "Najdłuższe najpierw",
+ "@sortOrderLongestFirst": {}
}
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index 13d019e99..5e6222825 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -1380,5 +1380,15 @@
"explorerPageTitle": "Проводник",
"@explorerPageTitle": {},
"explorerActionSelectStorageVolume": "Выбрать хранилище",
- "@explorerActionSelectStorageVolume": {}
+ "@explorerActionSelectStorageVolume": {},
+ "selectStorageVolumeDialogTitle": "Выбрать хранилище",
+ "@selectStorageVolumeDialogTitle": {},
+ "sortByDuration": "По продолжительности",
+ "@sortByDuration": {},
+ "sortOrderLongestFirst": "Сначала самый длинный",
+ "@sortOrderLongestFirst": {},
+ "setHomeCustom": "По своему",
+ "@setHomeCustom": {},
+ "sortOrderShortestFirst": "Сначала самый короткий",
+ "@sortOrderShortestFirst": {}
}
diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb
index ee985d7a9..aa06fc045 100644
--- a/lib/l10n/app_sv.arb
+++ b/lib/l10n/app_sv.arb
@@ -1,5 +1,5 @@
{
- "viewerActionLock": "Lås",
+ "viewerActionLock": "Lås visaren",
"@viewerActionLock": {},
"entryInfoActionEditTags": "Redigera taggar",
"@entryInfoActionEditTags": {},
@@ -7,19 +7,19 @@
"@videoActionPlay": {},
"viewerActionSettings": "Inställningar",
"@viewerActionSettings": {},
- "albumTierSpecial": "Vanlig",
+ "albumTierSpecial": "Vanligt förekommande",
"@albumTierSpecial": {},
"displayRefreshRatePreferLowest": "Lägsta intervall",
"@displayRefreshRatePreferLowest": {},
- "keepScreenOnViewerOnly": "Visningssidan bara",
+ "keepScreenOnViewerOnly": "Endast visningssidan",
"@keepScreenOnViewerOnly": {},
- "mapStyleOsmHot": "Humanitarian OSM",
+ "mapStyleOsmHot": "Humanitär OSM",
"@mapStyleOsmHot": {},
"videoResumptionModeAlways": "Alltid",
"@videoResumptionModeAlways": {},
"storageVolumeDescriptionFallbackPrimary": "Intern lagring",
"@storageVolumeDescriptionFallbackPrimary": {},
- "widgetOpenPageCollection": "Öppen insamling",
+ "widgetOpenPageCollection": "Öppna samling",
"@widgetOpenPageCollection": {},
"widgetTapUpdateWidget": "Uppdatera widgeten",
"@widgetTapUpdateWidget": {},
@@ -206,7 +206,7 @@
"@entryActionDelete": {},
"entryActionCopyToClipboard": "Spara till urklipp",
"@entryActionCopyToClipboard": {},
- "viewerActionUnlock": "Öppna",
+ "viewerActionUnlock": "Lås upp visaren",
"@viewerActionUnlock": {},
"slideshowActionResume": "Återuppta",
"@slideshowActionResume": {},
@@ -238,9 +238,9 @@
"@cropAspectRatioOriginal": {},
"cropAspectRatioSquare": "Fyrkant",
"@cropAspectRatioSquare": {},
- "filterAspectRatioLandscapeLabel": "Liggande",
+ "filterAspectRatioLandscapeLabel": "Liggande bilder",
"@filterAspectRatioLandscapeLabel": {},
- "filterAspectRatioPortraitLabel": "Porträtt",
+ "filterAspectRatioPortraitLabel": "Stående bilder",
"@filterAspectRatioPortraitLabel": {},
"filterBinLabel": "Papperskorg",
"@filterBinLabel": {},
@@ -333,9 +333,9 @@
"@mapStyleGoogleNormal": {},
"mapStyleGoogleHybrid": "Google Maps (Hybrid)",
"@mapStyleGoogleHybrid": {},
- "mapStyleGoogleTerrain": "Google Maps (Terrain)",
+ "mapStyleGoogleTerrain": "Google Maps (Terräng)",
"@mapStyleGoogleTerrain": {},
- "mapStyleStamenWatercolor": "Stamen Watercolor",
+ "mapStyleStamenWatercolor": "Stamen Watercolor (Akvarell)",
"@mapStyleStamenWatercolor": {},
"maxBrightnessNever": "Alldrig",
"@maxBrightnessNever": {},
@@ -377,11 +377,11 @@
"@settingsVideoEnablePip": {},
"videoControlsPlay": "Spela",
"@videoControlsPlay": {},
- "videoControlsPlaySeek": "Spela & sök bakåt/framåt",
+ "videoControlsPlaySeek": "Spela & spola bakåt/framåt",
"@videoControlsPlaySeek": {},
"videoControlsPlayOutside": "Öppna med annan spelare",
"@videoControlsPlayOutside": {},
- "videoControlsNone": "Ingen",
+ "videoControlsNone": "Inga",
"@videoControlsNone": {},
"videoLoopModeNever": "Aldrig",
"@videoLoopModeNever": {},
@@ -409,9 +409,9 @@
"@widgetDisplayedItemRandom": {},
"widgetDisplayedItemMostRecent": "Alldra senast",
"@widgetDisplayedItemMostRecent": {},
- "widgetOpenPageHome": "Öppna hem",
+ "widgetOpenPageHome": "Öppna startsida",
"@widgetOpenPageHome": {},
- "otherDirectoryDescription": "“{name}” map",
+ "otherDirectoryDescription": "“{name}”-katalogen",
"@otherDirectoryDescription": {
"placeholders": {
"name": {
@@ -421,7 +421,7 @@
}
}
},
- "storageAccessDialogMessage": "Var snäll och välj {directory} av“{volume}” på nästa skärm för att ge appen åtkomst till den.",
+ "storageAccessDialogMessage": "Vänligen välj {directory} i “{volume}” på nästa skärm för att ge appen åtkomst till den.",
"@storageAccessDialogMessage": {
"placeholders": {
"directory": {
@@ -523,7 +523,7 @@
"@nameConflictDialogSingleSourceMessage": {},
"nameConflictDialogMultipleSourceMessage": "Vissa filer har samma namn.",
"@nameConflictDialogMultipleSourceMessage": {},
- "noMatchingAppDialogMessage": "Det finns inga appar som kan hantera detta.",
+ "noMatchingAppDialogMessage": "Det finns inga applikationer som kan hantera detta.",
"@noMatchingAppDialogMessage": {},
"moveUndatedConfirmationDialogSetDate": "Spara datum",
"@moveUndatedConfirmationDialogSetDate": {},
@@ -752,7 +752,7 @@
"@viewerTransitionFade": {},
"wallpaperTargetHomeLock": "Hem och låsskärmar",
"@wallpaperTargetHomeLock": {},
- "missingSystemFilePickerDialogMessage": "systemets filväljare är borta eller avstängd. Var snäll och sätt på den och försök igen.",
+ "missingSystemFilePickerDialogMessage": "Systemets filväljare saknas eller har inaktiverats. Vänligen aktivera denna och försök igen.",
"@missingSystemFilePickerDialogMessage": {},
"renameProcessorCounter": "Räknare",
"@renameProcessorCounter": {},
@@ -833,5 +833,66 @@
"chipActionUnpin": "Släpp från fästet",
"@chipActionUnpin": {},
"chipActionShowCollection": "Visa i samling",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "videoActionABRepeat": "A-B återupprepa",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetStart": "Ange start",
+ "@videoRepeatActionSetStart": {},
+ "restrictedAccessDialogMessage": "Denna applikation har ej tillåtelse att modifiera filer i {directory} i \"{volume}\".\n\nVänligen använd en förinstallerad filhanterare eller galleriapplikation för att flytta filerna till en annan katalog.",
+ "@restrictedAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "widgetOpenPageViewer": "Öppna bildspel",
+ "@widgetOpenPageViewer": {},
+ "rootDirectoryDescription": "grundkatalog",
+ "@rootDirectoryDescription": {},
+ "notEnoughSpaceDialogMessage": "Denna åtgärd behöver {neededSize} ledigt utrymme på \"{volume}\" för att kunna slutföras, men det är enbart {freeSize} kvar.",
+ "@notEnoughSpaceDialogMessage": {
+ "placeholders": {
+ "neededSize": {
+ "type": "String",
+ "example": "314 MB"
+ },
+ "freeSize": {
+ "type": "String",
+ "example": "123 MB"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "addShortcutButtonLabel": "LÄGG TILL",
+ "@addShortcutButtonLabel": {},
+ "addShortcutDialogLabel": "Rubrik för genväg",
+ "@addShortcutDialogLabel": {},
+ "unsupportedTypeDialogMessage": "{count, plural, =1{Denna åtgärd stöds ej för filer av denna typ: {types}.} other{Denna åtgärd stöds ej för följande typer av filer: {types}.}}",
+ "@unsupportedTypeDialogMessage": {
+ "placeholders": {
+ "count": {},
+ "types": {
+ "type": "String",
+ "example": "GIF, TIFF, MP4",
+ "description": "a list of unsupported types"
+ }
+ }
+ },
+ "stopTooltip": "Stopp",
+ "@stopTooltip": {},
+ "chipActionGoToExplorerPage": "Visa i utforskaren",
+ "@chipActionGoToExplorerPage": {},
+ "videoRepeatActionSetEnd": "Ange slut",
+ "@videoRepeatActionSetEnd": {}
}
diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb
index 8c29b282d..67513441a 100644
--- a/lib/l10n/app_tr.arb
+++ b/lib/l10n/app_tr.arb
@@ -7,7 +7,7 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Hüküm ve koşulları kabul ediyorum",
"@welcomeTermsToggle": {},
- "itemCount": "{count, plural, other{{count} öğe}}",
+ "itemCount": "{count, plural, other{{count} öge}}",
"@itemCount": {},
"timeSeconds": "{count, plural, other{{count} saniye}}",
"@timeSeconds": {},
@@ -273,7 +273,7 @@
"@rootDirectoryDescription": {},
"otherDirectoryDescription": "“{name}” dizin",
"@otherDirectoryDescription": {},
- "storageAccessDialogMessage": "Bu uygulamaya erişim sağlamak için lütfen bir sonraki ekranda “{volume}” öğesinin {directory} dizinini seçin.",
+ "storageAccessDialogMessage": "Bir sonraki ekranda \"{volume}\" içindeki {directory} dizinini seçerek bu uygulamaya erişim izni verin.",
"@storageAccessDialogMessage": {},
"restrictedAccessDialogMessage": "Bu uygulamanın “{volume}” içindeki {directory} dosyaları değiştirmesine izin verilmiyor.\n\nÖğeleri başka bir dizine taşımak için lütfen önceden yüklenmiş bir dosya yöneticisi veya galeri uygulaması kullanın.",
"@restrictedAccessDialogMessage": {},
@@ -281,7 +281,7 @@
"@notEnoughSpaceDialogMessage": {},
"missingSystemFilePickerDialogMessage": "Sistem dosya seçicisi eksik veya devre dışı. Lütfen etkinleştirin ve tekrar deneyin.",
"@missingSystemFilePickerDialogMessage": {},
- "unsupportedTypeDialogMessage": "{count, plural, =1{Bu işlem aşağıdaki türdeki öğeler için desteklenmez: {types}.} other{Bu işlem aşağıdaki türlerdeki öğeler için desteklenmez: {types}.}}",
+ "unsupportedTypeDialogMessage": "{count, plural, =1{Bu işlem şu türdeki ögeler için desteklenmez: {types}.} other{Bu işlem şu türlerdeki ögeler için desteklenmez: {types}.}}",
"@unsupportedTypeDialogMessage": {},
"nameConflictDialogSingleSourceMessage": "Hedef klasördeki bazı dosyalar aynı ada sahip.",
"@nameConflictDialogSingleSourceMessage": {},
@@ -293,11 +293,11 @@
"@addShortcutButtonLabel": {},
"noMatchingAppDialogMessage": "Bununla ilgilenebilecek bir uygulama yok.",
"@noMatchingAppDialogMessage": {},
- "binEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öğe geri dönüşüm kutusuna taşınsın mı?} other{Bu {count} madde geri dönüşüm kutusuna atılsın mı?}}",
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öge geri dönüşüm kutusuna taşınsın mı?} other{Bu {count} öge geri dönüşüm kutusuna atılsın mı?}}",
"@binEntriesConfirmationDialogMessage": {},
- "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öğe silinsin mi?} other{Bu {count} öğe silinsin mi?}}",
+ "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öge silinsin mi?} other{Bu {count} öge silinsin mi?}}",
"@deleteEntriesConfirmationDialogMessage": {},
- "moveUndatedConfirmationDialogMessage": "Devam etmeden önce öğe tarihleri kaydedilsin mi?",
+ "moveUndatedConfirmationDialogMessage": "Devam etmeden önce öge tarihleri kaydedilsin mi?",
"@moveUndatedConfirmationDialogMessage": {},
"moveUndatedConfirmationDialogSetDate": "Tarihleri kaydet",
"@moveUndatedConfirmationDialogSetDate": {},
@@ -307,7 +307,7 @@
"@videoStartOverButtonLabel": {},
"videoResumeButtonLabel": "SÜRDÜR",
"@videoResumeButtonLabel": {},
- "setCoverDialogLatest": "Son öğe",
+ "setCoverDialogLatest": "Son öge",
"@setCoverDialogLatest": {},
"setCoverDialogAuto": "Otomatik",
"@setCoverDialogAuto": {},
@@ -359,7 +359,7 @@
"@editEntryDateDialogSetCustom": {},
"editEntryDateDialogCopyField": "Başka bir tarihten kopyala",
"@editEntryDateDialogCopyField": {},
- "editEntryDialogCopyFromItem": "Başka bir öğeden kopyala",
+ "editEntryDialogCopyFromItem": "Başka bir ögeden kopyala",
"@editEntryDialogCopyFromItem": {},
"editEntryDateDialogExtractFromTitle": "Başlıktan ayıkla",
"@editEntryDateDialogExtractFromTitle": {},
@@ -523,25 +523,25 @@
"@dateYesterday": {},
"dateThisMonth": "Bu ay",
"@dateThisMonth": {},
- "collectionDeleteFailureFeedback": "{count, plural, =1{1 öğe silinemedi} other{{count} öğe silinemedi}}",
+ "collectionDeleteFailureFeedback": "{count, plural, =1{1 öge silinemedi} other{{count} öge silinemedi}}",
"@collectionDeleteFailureFeedback": {},
- "collectionCopyFailureFeedback": "{count, plural, =1{1 öğe kopyalanamadı} other{{count} öğe kopyalanamadı}}",
+ "collectionCopyFailureFeedback": "{count, plural, =1{1 öge kopyalanamadı} other{{count} öge kopyalanamadı}}",
"@collectionCopyFailureFeedback": {},
- "collectionMoveFailureFeedback": "{count, plural, =1{1 öğe taşınamadı} other{{count} öğe taşınamadı}}",
+ "collectionMoveFailureFeedback": "{count, plural, =1{1 öge taşınamadı} other{{count} öge taşınamadı}}",
"@collectionMoveFailureFeedback": {},
- "collectionRenameFailureFeedback": "{count, plural, =1{1 öğenin adı değiştirilemedi} other{{count} öğenin adı değiştirilemedi}}",
+ "collectionRenameFailureFeedback": "{count, plural, =1{1 ögenin adı değiştirilemedi} other{{count} ögenin adı değiştirilemedi}}",
"@collectionRenameFailureFeedback": {},
- "collectionEditFailureFeedback": "{count, plural, =1{1 öğe düzenlenemedi} other{{count} öğe düzenlenemedi}}",
+ "collectionEditFailureFeedback": "{count, plural, =1{1 öge düzenlenemedi} other{{count} öge düzenlenemedi}}",
"@collectionEditFailureFeedback": {},
"collectionExportFailureFeedback": "{count, plural, =1{1 sayfa dışa aktarılamadı} other{{count} sayfa dışa aktarılamadı}}",
"@collectionExportFailureFeedback": {},
- "collectionCopySuccessFeedback": "{count, plural, =1{1 öğe kopyalandı} other{{count} öğe kopyalandı}}",
+ "collectionCopySuccessFeedback": "{count, plural, =1{1 öge kopyalandı} other{{count} öge kopyalandı}}",
"@collectionCopySuccessFeedback": {},
- "collectionMoveSuccessFeedback": "{count, plural, =1{1 öğe taşındı} other{{count} öğe taşındı}}",
+ "collectionMoveSuccessFeedback": "{count, plural, =1{1 öge taşındı} other{{count} öge taşındı}}",
"@collectionMoveSuccessFeedback": {},
- "collectionRenameSuccessFeedback": "{count, plural, =1{1 öğenin adı değiştirildi} other{{count} öğenin adı değiştirildi}}",
+ "collectionRenameSuccessFeedback": "{count, plural, =1{1 ögenin adı değiştirildi} other{{count} ögenin adı değiştirildi}}",
"@collectionRenameSuccessFeedback": {},
- "collectionEditSuccessFeedback": "{count, plural, =1{1 öğe düzenlendi} other{{count} öğe düzenlendi}}",
+ "collectionEditSuccessFeedback": "{count, plural, =1{1 öge düzenlendi} other{{count} öge düzenlendi}}",
"@collectionEditSuccessFeedback": {},
"collectionEmptyFavourites": "Favori yok",
"@collectionEmptyFavourites": {},
@@ -705,7 +705,7 @@
"@settingsNavigationDrawerTile": {},
"settingsNavigationDrawerEditorPageTitle": "Gezinti Menüsü",
"@settingsNavigationDrawerEditorPageTitle": {},
- "settingsNavigationDrawerBanner": "Menü öğelerini taşımak ve yeniden sıralamak için dokunun ve basılı tutun.",
+ "settingsNavigationDrawerBanner": "Menü ögelerini taşımak ve yeniden sıralamak için dokunun ve basılı tutun.",
"@settingsNavigationDrawerBanner": {},
"settingsNavigationDrawerTabTypes": "Türler",
"@settingsNavigationDrawerTabTypes": {},
@@ -743,9 +743,9 @@
"@settingsCollectionQuickActionTabBrowsing": {},
"settingsCollectionQuickActionTabSelecting": "Seçme",
"@settingsCollectionQuickActionTabSelecting": {},
- "settingsCollectionBrowsingQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğelere göz atarken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.",
+ "settingsCollectionBrowsingQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve ögelere göz atarken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.",
"@settingsCollectionBrowsingQuickActionEditorBanner": {},
- "settingsCollectionSelectionQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğeleri seçerken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.",
+ "settingsCollectionSelectionQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve ögeleri seçerken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.",
"@settingsCollectionSelectionQuickActionEditorBanner": {},
"settingsViewerSectionTitle": "Görüntüleyici",
"@settingsViewerSectionTitle": {},
@@ -851,9 +851,9 @@
"@settingsSaveSearchHistory": {},
"settingsEnableBin": "Geri dönüşüm kutusunu kullan",
"@settingsEnableBin": {},
- "settingsEnableBinSubtitle": "Silinen öğeleri 30 gün boyunca saklar",
+ "settingsEnableBinSubtitle": "Silinen ögeleri 30 gün boyunca saklar",
"@settingsEnableBinSubtitle": {},
- "settingsHiddenItemsTile": "Gizli öğeler",
+ "settingsHiddenItemsTile": "Gizli ögeler",
"@settingsHiddenItemsTile": {},
"settingsHiddenItemsPageTitle": "Gizli Öğeler",
"@settingsHiddenItemsPageTitle": {},
@@ -921,7 +921,7 @@
"@settingsCollectionTile": {},
"statsPageTitle": "İstatistikler",
"@statsPageTitle": {},
- "statsWithGps": "{count, plural, =1{1 konuma sahip öğe} other{{count} konuma sahip öğe}}",
+ "statsWithGps": "{count, plural, =1{1 öge konuma sahip} other{{count} öge konuma sahip}}",
"@statsWithGps": {},
"statsTopCountriesSectionTitle": "Öne Çıkan Ülkeler",
"@statsTopCountriesSectionTitle": {},
@@ -1019,7 +1019,7 @@
"@filePickerDoNotShowHiddenFiles": {},
"filePickerOpenFrom": "Şuradan aç",
"@filePickerOpenFrom": {},
- "filePickerNoItems": "Öğe yok",
+ "filePickerNoItems": "Öge yok",
"@filePickerNoItems": {},
"filePickerUseThisFolder": "Bu klasörü kullan",
"@filePickerUseThisFolder": {},
@@ -1049,7 +1049,7 @@
"@sortOrderLargestFirst": {},
"settingsConfirmationAfterMoveToBinItems": "Öğeleri geri dönüşüm kutusuna taşıdıktan sonra mesaj göster",
"@settingsConfirmationAfterMoveToBinItems": {},
- "settingsViewerGestureSideTapNext": "Önceki/sonraki öğeyi göstermek için ekran kenarlarına dokunun",
+ "settingsViewerGestureSideTapNext": "Önceki/sonraki ögeyi göstermek için ekran kenarlarına dokunun",
"@settingsViewerGestureSideTapNext": {},
"settingsSlideshowVideoPlaybackTile": "Video oynatma",
"@settingsSlideshowVideoPlaybackTile": {},
@@ -1161,7 +1161,7 @@
"@widgetDisplayedItemMostRecent": {},
"settingsSubtitleThemeTextPositionTile": "Metin konumu",
"@settingsSubtitleThemeTextPositionTile": {},
- "settingsWidgetDisplayedItem": "Görüntülenen öğe",
+ "settingsWidgetDisplayedItem": "Görüntülenen öge",
"@settingsWidgetDisplayedItem": {},
"settingsSubtitleThemeTextPositionDialogTitle": "Metin Konumu",
"@settingsSubtitleThemeTextPositionDialogTitle": {},
@@ -1378,5 +1378,17 @@
"chipActionGoToExplorerPage": "Gezginde göster",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Gezgin",
- "@explorerPageTitle": {}
+ "@explorerPageTitle": {},
+ "explorerActionSelectStorageVolume": "Depolama alanı seç",
+ "@explorerActionSelectStorageVolume": {},
+ "selectStorageVolumeDialogTitle": "Depolama Alanı Seç",
+ "@selectStorageVolumeDialogTitle": {},
+ "setHomeCustom": "Özel",
+ "@setHomeCustom": {},
+ "sortByDuration": "Süreye göre",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "Önce en kısa",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "Önce en uzun",
+ "@sortOrderLongestFirst": {}
}
diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb
index cdfae5627..c3f6b246f 100644
--- a/lib/l10n/app_uk.arb
+++ b/lib/l10n/app_uk.arb
@@ -1542,5 +1542,11 @@
"explorerActionSelectStorageVolume": "Обрати сховище",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Оберіть сховище",
- "@selectStorageVolumeDialogTitle": {}
+ "@selectStorageVolumeDialogTitle": {},
+ "sortByDuration": "За тривалістю",
+ "@sortByDuration": {},
+ "sortOrderShortestFirst": "Спершу найкоротше",
+ "@sortOrderShortestFirst": {},
+ "sortOrderLongestFirst": "Спершу найдовше",
+ "@sortOrderLongestFirst": {}
}
diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb
index 43039c451..444d67bc2 100644
--- a/lib/l10n/app_zh.arb
+++ b/lib/l10n/app_zh.arb
@@ -1378,5 +1378,17 @@
"explorerPageTitle": "资源管理器",
"@explorerPageTitle": {},
"chipActionGoToExplorerPage": "在资源管理器中显示",
- "@chipActionGoToExplorerPage": {}
+ "@chipActionGoToExplorerPage": {},
+ "setHomeCustom": "自定义",
+ "@setHomeCustom": {},
+ "explorerActionSelectStorageVolume": "选择存储器",
+ "@explorerActionSelectStorageVolume": {},
+ "selectStorageVolumeDialogTitle": "选择存储器",
+ "@selectStorageVolumeDialogTitle": {},
+ "sortByDuration": "按时长",
+ "@sortByDuration": {},
+ "sortOrderLongestFirst": "先长后短",
+ "@sortOrderLongestFirst": {},
+ "sortOrderShortestFirst": "先短后长",
+ "@sortOrderShortestFirst": {}
}
diff --git a/lib/main_play_test_editor.dart b/lib/main_play_test_editor.dart
index 95e5f4d6c..01ef9fbfa 100644
--- a/lib/main_play_test_editor.dart
+++ b/lib/main_play_test_editor.dart
@@ -1,6 +1,6 @@
import 'package:aves/app_flavor.dart';
import 'package:aves/main_common.dart';
-import 'package:aves/widgets/intent.dart';
+import 'package:aves/model/app/intent.dart';
// https://developer.android.com/studio/command-line/adb.html#IntentSpec
// adb shell am start -n deckers.thibault.aves.debug/deckers.thibault.aves.MainActivity -a android.intent.action.EDIT -d content://media/external/images/media/183128 -t image/*
diff --git a/lib/model/app/contributors.dart b/lib/model/app/contributors.dart
index 925f81886..488edc60a 100644
--- a/lib/model/app/contributors.dart
+++ b/lib/model/app/contributors.dart
@@ -95,6 +95,7 @@ class Contributors {
Contributor('elfriob', 'elfriob@ya.ru'),
Contributor('Stephan Paternotte', 'stephan@paternottes.net'),
Contributor('Tung Anh', 'buihuutunganh2007@gmail.com'),
+ Contributor('Adrien N', 'adriennathaniel1999@gmail.com'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
@@ -113,6 +114,7 @@ class Contributors {
// Contributor('Prasanta-Hembram', 'Prasantahembram720@gmail.com'), // Santali
// Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian
// Contributor('Shift18', 'bribable.lawyer@posteo.net'), // Swedish
+ // Contributor('Andreas Håll', 'ante_skalman@hotmail.com'), // Swedish
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
};
}
diff --git a/lib/model/app/dependencies.dart b/lib/model/app/dependencies.dart
index cc0f252b6..6492429e6 100644
--- a/lib/model/app/dependencies.dart
+++ b/lib/model/app/dependencies.dart
@@ -338,12 +338,6 @@ class Dependencies {
license: apache2,
sourceUrl: 'https://github.com/jifalops/dart-latlong',
),
- Dependency(
- name: 'Material Color Utilities',
- license: apache2,
- licenseUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart/LICENSE',
- sourceUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart',
- ),
Dependency(
name: 'Memory Leak Tracker',
license: bsd3,
diff --git a/lib/widgets/intent.dart b/lib/model/app/intent.dart
similarity index 100%
rename from lib/widgets/intent.dart
rename to lib/model/app/intent.dart
diff --git a/lib/model/apps.dart b/lib/model/app_inventory.dart
similarity index 100%
rename from lib/model/apps.dart
rename to lib/model/app_inventory.dart
diff --git a/lib/model/covers.dart b/lib/model/covers.dart
index 340b8f8cc..eadd26621 100644
--- a/lib/model/covers.dart
+++ b/lib/model/covers.dart
@@ -1,6 +1,6 @@
import 'dart:async';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
diff --git a/lib/model/db/db_metadata.dart b/lib/model/db/db_metadata.dart
index 524efd299..c9216c3f1 100644
--- a/lib/model/db/db_metadata.dart
+++ b/lib/model/db/db_metadata.dart
@@ -6,7 +6,7 @@ import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata/trash.dart';
import 'package:aves/model/vaults/details.dart';
-import 'package:aves/model/video_playback.dart';
+import 'package:aves/model/viewer/video_playback.dart';
abstract class MetadataDb {
int get nextId;
diff --git a/lib/model/db/db_metadata_sqflite.dart b/lib/model/db/db_metadata_sqflite.dart
index e8903a076..9c2191a11 100644
--- a/lib/model/db/db_metadata_sqflite.dart
+++ b/lib/model/db/db_metadata_sqflite.dart
@@ -10,7 +10,7 @@ import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata/trash.dart';
import 'package:aves/model/vaults/details.dart';
-import 'package:aves/model/video_playback.dart';
+import 'package:aves/model/viewer/video_playback.dart';
import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
diff --git a/lib/model/entry/extensions/catalog.dart b/lib/model/entry/extensions/catalog.dart
index 94dd3b148..fc158c122 100644
--- a/lib/model/entry/extensions/catalog.dart
+++ b/lib/model/entry/extensions/catalog.dart
@@ -2,9 +2,9 @@ import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart';
-import 'package:aves/model/geotiff.dart';
+import 'package:aves/model/media/geotiff.dart';
import 'package:aves/model/metadata/catalog.dart';
-import 'package:aves/model/video/metadata.dart';
+import 'package:aves/model/media/video/metadata.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart';
diff --git a/lib/model/entry/extensions/info.dart b/lib/model/entry/extensions/info.dart
index 115a5bb9f..0b2f9deda 100644
--- a/lib/model/entry/extensions/info.dart
+++ b/lib/model/entry/extensions/info.dart
@@ -4,7 +4,7 @@ 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/video/metadata.dart';
+import 'package:aves/model/media/video/metadata.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart';
diff --git a/lib/model/entry/sort.dart b/lib/model/entry/sort.dart
index f15521bc0..5cf6aa267 100644
--- a/lib/model/entry/sort.dart
+++ b/lib/model/entry/sort.dart
@@ -35,4 +35,12 @@ class AvesEntrySort {
final c = (b.sizeBytes ?? 0).compareTo(a.sizeBytes ?? 0);
return c != 0 ? c : compareByDate(a, b);
}
+
+ // compare by:
+ // 1) duration descending
+ // 2) date descending
+ static int compareByDuration(AvesEntry a, AvesEntry b) {
+ final c = (b.durationMillis ?? 0).compareTo(a.durationMillis ?? 0);
+ return c != 0 ? c : compareByDate(a, b);
+ }
}
diff --git a/lib/model/geotiff.dart b/lib/model/media/geotiff.dart
similarity index 100%
rename from lib/model/geotiff.dart
rename to lib/model/media/geotiff.dart
diff --git a/lib/model/panorama.dart b/lib/model/media/panorama.dart
similarity index 100%
rename from lib/model/panorama.dart
rename to lib/model/media/panorama.dart
diff --git a/lib/model/video/channel_layouts.dart b/lib/model/media/video/channel_layouts.dart
similarity index 100%
rename from lib/model/video/channel_layouts.dart
rename to lib/model/media/video/channel_layouts.dart
diff --git a/lib/model/video/codecs.dart b/lib/model/media/video/codecs.dart
similarity index 100%
rename from lib/model/video/codecs.dart
rename to lib/model/media/video/codecs.dart
diff --git a/lib/model/video/metadata.dart b/lib/model/media/video/metadata.dart
similarity index 98%
rename from lib/model/video/metadata.dart
rename to lib/model/media/video/metadata.dart
index 6ae8ba583..3349e202f 100644
--- a/lib/model/video/metadata.dart
+++ b/lib/model/media/video/metadata.dart
@@ -2,11 +2,11 @@ import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/metadata/catalog.dart';
-import 'package:aves/model/video/channel_layouts.dart';
-import 'package:aves/model/video/codecs.dart';
-import 'package:aves/model/video/profiles/aac.dart';
-import 'package:aves/model/video/profiles/h264.dart';
-import 'package:aves/model/video/profiles/hevc.dart';
+import 'package:aves/model/media/video/channel_layouts.dart';
+import 'package:aves/model/media/video/codecs.dart';
+import 'package:aves/model/media/video/profiles/aac.dart';
+import 'package:aves/model/media/video/profiles/h264.dart';
+import 'package:aves/model/media/video/profiles/hevc.dart';
import 'package:aves/ref/languages.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/ref/mime_types.dart';
diff --git a/lib/model/video/profiles/aac.dart b/lib/model/media/video/profiles/aac.dart
similarity index 100%
rename from lib/model/video/profiles/aac.dart
rename to lib/model/media/video/profiles/aac.dart
diff --git a/lib/model/video/profiles/h264.dart b/lib/model/media/video/profiles/h264.dart
similarity index 100%
rename from lib/model/video/profiles/h264.dart
rename to lib/model/media/video/profiles/h264.dart
diff --git a/lib/model/video/profiles/hevc.dart b/lib/model/media/video/profiles/hevc.dart
similarity index 100%
rename from lib/model/video/profiles/hevc.dart
rename to lib/model/media/video/profiles/hevc.dart
diff --git a/lib/model/naming_pattern.dart b/lib/model/naming_pattern.dart
index c31c16143..2305f94c2 100644
--- a/lib/model/naming_pattern.dart
+++ b/lib/model/naming_pattern.dart
@@ -1,4 +1,4 @@
-import 'package:aves/convert/metadata/fields.dart';
+import 'package:aves/convert/convert.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves_model/aves_model.dart';
diff --git a/lib/model/settings/modules/app.dart b/lib/model/settings/modules/app.dart
index e3bb24f23..60f968d21 100644
--- a/lib/model/settings/modules/app.dart
+++ b/lib/model/settings/modules/app.dart
@@ -6,7 +6,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';
mixin AppSettings on SettingsAccess {
- static const int _recentFilterHistoryMax = 10;
+ static const int recentFilterHistoryMax = 20;
bool get hasAcceptedTerms => getBool(SettingKeys.hasAcceptedTermsKey) ?? SettingsDefaults.hasAcceptedTerms;
@@ -105,9 +105,9 @@ mixin AppSettings on SettingsAccess {
List get recentDestinationAlbums => getStringList(SettingKeys.recentDestinationAlbumsKey) ?? [];
- set recentDestinationAlbums(List newValue) => set(SettingKeys.recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList());
+ set recentDestinationAlbums(List newValue) => set(SettingKeys.recentDestinationAlbumsKey, newValue.take(recentFilterHistoryMax).toList());
List get recentTags => (getStringList(SettingKeys.recentTagsKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
- set recentTags(List newValue) => set(SettingKeys.recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
+ set recentTags(List newValue) => set(SettingKeys.recentTagsKey, newValue.take(recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
}
diff --git a/lib/model/settings/modules/privacy.dart b/lib/model/settings/modules/privacy.dart
index 90a4b6a97..c9c2ff926 100644
--- a/lib/model/settings/modules/privacy.dart
+++ b/lib/model/settings/modules/privacy.dart
@@ -9,17 +9,18 @@ mixin PrivacySettings on SettingsAccess, SearchSettings {
set hiddenFilters(Set newValue) => set(SettingKeys.hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList());
void changeFilterVisibility(Set filters, bool visible) {
+ final _deactivatedHiddenFilters = deactivatedHiddenFilters;
final _hiddenFilters = hiddenFilters;
+
+ _deactivatedHiddenFilters.removeAll(filters);
if (visible) {
_hiddenFilters.removeAll(filters);
} else {
_hiddenFilters.addAll(filters);
searchHistory = searchHistory..removeWhere(filters.contains);
-
- final _deactivatedHiddenFilters = deactivatedHiddenFilters;
- _deactivatedHiddenFilters.removeAll(filters);
- deactivatedHiddenFilters = _deactivatedHiddenFilters;
}
+
+ deactivatedHiddenFilters = _deactivatedHiddenFilters;
hiddenFilters = _hiddenFilters;
}
@@ -29,14 +30,18 @@ mixin PrivacySettings on SettingsAccess, SearchSettings {
void activateHiddenFilter(CollectionFilter filter, bool active) {
final _deactivatedHiddenFilters = deactivatedHiddenFilters;
+ final _hiddenFilters = hiddenFilters;
+
if (active) {
_deactivatedHiddenFilters.remove(filter);
+ _hiddenFilters.add(filter);
+ searchHistory = searchHistory..remove(filter);
} else {
_deactivatedHiddenFilters.add(filter);
+ _hiddenFilters.remove(filter);
}
- deactivatedHiddenFilters = _deactivatedHiddenFilters;
- final visible = !active;
- changeFilterVisibility({filter}, visible);
+ deactivatedHiddenFilters = _deactivatedHiddenFilters;
+ hiddenFilters = _hiddenFilters;
}
}
diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart
index e6329dbd7..d0f8d88f0 100644
--- a/lib/model/source/collection_lens.dart
+++ b/lib/model/source/collection_lens.dart
@@ -161,6 +161,7 @@ class CollectionLens with ChangeNotifier {
case EntrySortFactor.rating:
return !filters.any((f) => f is RatingFilter);
case EntrySortFactor.size:
+ case EntrySortFactor.duration:
return false;
}
}
@@ -261,6 +262,8 @@ class CollectionLens with ChangeNotifier {
_filteredSortedEntries.sort(AvesEntrySort.compareByRating);
case EntrySortFactor.size:
_filteredSortedEntries.sort(AvesEntrySort.compareBySize);
+ case EntrySortFactor.duration:
+ _filteredSortedEntries.sort(AvesEntrySort.compareByDuration);
}
if (sortReverse) {
_filteredSortedEntries = _filteredSortedEntries.reversed.toList();
@@ -294,6 +297,7 @@ class CollectionLens with ChangeNotifier {
case EntrySortFactor.rating:
sections = groupBy(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
case EntrySortFactor.size:
+ case EntrySortFactor.duration:
sections = Map.fromEntries([
MapEntry(const SectionKey(), _filteredSortedEntries),
]);
diff --git a/lib/model/video_playback.dart b/lib/model/viewer/video_playback.dart
similarity index 100%
rename from lib/model/video_playback.dart
rename to lib/model/viewer/video_playback.dart
diff --git a/lib/model/view_state.dart b/lib/model/viewer/view_state.dart
similarity index 100%
rename from lib/model/view_state.dart
rename to lib/model/viewer/view_state.dart
diff --git a/lib/services/app_service.dart b/lib/services/app_service.dart
index 774dacedc..370275c60 100644
--- a/lib/services/app_service.dart
+++ b/lib/services/app_service.dart
@@ -1,6 +1,6 @@
import 'dart:async';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/filters/filters.dart';
diff --git a/lib/services/media/media_edit_service.dart b/lib/services/media/media_edit_service.dart
index fd3810e97..a1961e738 100644
--- a/lib/services/media/media_edit_service.dart
+++ b/lib/services/media/media_edit_service.dart
@@ -118,11 +118,6 @@ class PlatformMediaEditService implements MediaEditService {
required String destinationAlbum,
required NameConflictStrategy nameConflictStrategy,
}) {
- // TODO TLAD remove log when OOMs are inspected
- entries.where((v) => (v.sizeBytes ?? 0) > 20000000).forEach((entry) {
- reportService.log('convert large entry=$entry size=${entry.sizeBytes}');
- });
-
try {
return _opStream
.receiveBroadcastStream({
diff --git a/lib/services/metadata/metadata_edit_service.dart b/lib/services/metadata/metadata_edit_service.dart
index d19d7dc06..463698ac2 100644
--- a/lib/services/metadata/metadata_edit_service.dart
+++ b/lib/services/metadata/metadata_edit_service.dart
@@ -80,11 +80,6 @@ class PlatformMetadataEditService implements MetadataEditService {
Map metadata, {
bool autoCorrectTrailerOffset = true,
}) async {
- // TODO TLAD remove log when OOMs are inspected
- if ((entry.sizeBytes ?? 0) > 20000000) {
- await reportService.log('edit metadata of large entry=$entry size=${entry.sizeBytes}');
- }
-
try {
final result = await _platform.invokeMethod('editMetadata', {
'entry': entry.toPlatformEntryMap(),
diff --git a/lib/services/metadata/metadata_fetch_service.dart b/lib/services/metadata/metadata_fetch_service.dart
index d89bff766..aa2283587 100644
--- a/lib/services/metadata/metadata_fetch_service.dart
+++ b/lib/services/metadata/metadata_fetch_service.dart
@@ -2,12 +2,11 @@ import 'package:aves/convert/convert.dart';
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/geotiff.dart';
+import 'package:aves/model/media/geotiff.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata/overlay.dart';
import 'package:aves/model/multipage.dart';
-import 'package:aves/model/panorama.dart';
-import 'package:aves/ref/mime_types.dart';
+import 'package:aves/model/media/panorama.dart';
import 'package:aves/services/common/service_policy.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/xmp.dart';
@@ -69,11 +68,6 @@ class PlatformMetadataFetchService implements MetadataFetchService {
Future getCatalogMetadata(AvesEntry entry, {bool background = false}) async {
if (entry.isSvg) return null;
- // TODO TLAD remove log when MP4/TIFF-related OOMs are fixed
- if ({MimeTypes.mp4, MimeTypes.tiff}.contains(entry.mimeType) && (entry.sizeBytes ?? 0) > 20000000) {
- await reportService.log('catalog large entry=$entry size=${entry.sizeBytes}');
- }
-
Future call() async {
try {
// returns map with:
diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart
index 5bd83f525..8e03c5a52 100644
--- a/lib/theme/durations.dart
+++ b/lib/theme/durations.dart
@@ -29,11 +29,10 @@ class ADurations {
// collection animations
static const filterBarRemovalAnimation = Duration(milliseconds: 400);
static const collectionOpOverlayAnimation = Duration(milliseconds: 300);
- static const sectionHeaderAnimation = Duration(milliseconds: 200);
- static const thumbnailOverlayAnimation = Duration(milliseconds: 200);
// search animations
static const filterRowExpandAnimation = Duration(milliseconds: 300);
+ static const searchBodyTransition = Duration(milliseconds: 300);
// viewer animations
static const thumbnailScrollerScrollAnimation = Duration(milliseconds: 200);
diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart
index 1aa37db63..485ffa8e2 100644
--- a/lib/theme/icons.dart
+++ b/lib/theme/icons.dart
@@ -28,6 +28,7 @@ class AIcons {
static const descriptionUntitled = Icons.comments_disabled_outlined;
static const disc = Icons.fiber_manual_record;
static const display = Icons.light_mode_outlined;
+ static const duration = Icons.timelapse_outlined;
static const error = Icons.error_outline;
static const explorer = Icons.account_tree_outlined;
static const folder = Icons.folder_outlined;
@@ -162,6 +163,8 @@ class AIcons {
static const zoomOut = Icons.remove_outlined;
static const collapse = Icons.expand_less_outlined;
static const expand = Icons.expand_more_outlined;
+ static const up = Icons.keyboard_arrow_up_outlined;
+ static const down = Icons.keyboard_arrow_down_outlined;
static const previous = Icons.chevron_left_outlined;
static const next = Icons.chevron_right_outlined;
diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart
index 0152bd1aa..327485eeb 100644
--- a/lib/utils/android_file_utils.dart
+++ b/lib/utils/android_file_utils.dart
@@ -1,4 +1,4 @@
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/vaults/vaults.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves_model/aves_model.dart';
@@ -7,6 +7,8 @@ import 'package:flutter/foundation.dart';
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
+enum _State { uninitialized, initializing, initialized }
+
class AndroidFileUtils {
// cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT
static const contentScheme = 'content';
@@ -27,13 +29,19 @@ class AndroidFileUtils {
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
late final Set videoCapturesPaths;
Set storageVolumes = {};
- bool _initialized = false;
+ _State _initialized = _State.uninitialized;
AndroidFileUtils._private();
Future init() async {
- if (_initialized) return;
+ if (_initialized == _State.uninitialized) {
+ _initialized = _State.initializing;
+ await _doInit();
+ _initialized = _State.initialized;
+ }
+ }
+ Future _doInit() async {
separator = pContext.separator;
await _initStorageVolumes();
vaultRoot = await storageService.getVaultRoot();
@@ -50,8 +58,6 @@ class AndroidFileUtils {
// from Aves
avesVideoCapturesPath,
};
-
- _initialized = true;
}
Future _initStorageVolumes() async {
diff --git a/lib/view/src/actions/explorer.dart b/lib/view/src/actions/explorer.dart
index 38fb845c3..9b5272637 100644
--- a/lib/view/src/actions/explorer.dart
+++ b/lib/view/src/actions/explorer.dart
@@ -9,6 +9,7 @@ extension ExtraExplorerActionView on ExplorerAction {
return switch (this) {
ExplorerAction.addShortcut => l10n.collectionActionAddShortcut,
ExplorerAction.setHome => l10n.collectionActionSetHome,
+ ExplorerAction.stats => l10n.menuActionStats,
};
}
@@ -18,6 +19,7 @@ extension ExtraExplorerActionView on ExplorerAction {
return switch (this) {
ExplorerAction.addShortcut => AIcons.addShortcut,
ExplorerAction.setHome => AIcons.home,
+ ExplorerAction.stats => AIcons.stats,
};
}
}
diff --git a/lib/view/src/actions/map.dart b/lib/view/src/actions/map.dart
index ee7db32fc..c4d22a684 100644
--- a/lib/view/src/actions/map.dart
+++ b/lib/view/src/actions/map.dart
@@ -8,6 +8,7 @@ extension ExtraMapActionView on MapAction {
final l10n = context.l10n;
return switch (this) {
MapAction.selectStyle => l10n.mapStyleTooltip,
+ MapAction.openMapApp => l10n.entryActionOpenMap,
MapAction.zoomIn => l10n.mapZoomInTooltip,
MapAction.zoomOut => l10n.mapZoomOutTooltip,
};
@@ -18,6 +19,7 @@ extension ExtraMapActionView on MapAction {
IconData _getIconData() {
return switch (this) {
MapAction.selectStyle => AIcons.layers,
+ MapAction.openMapApp => AIcons.openOutside,
MapAction.zoomIn => AIcons.zoomIn,
MapAction.zoomOut => AIcons.zoomOut,
};
diff --git a/lib/view/src/source/sort.dart b/lib/view/src/source/sort.dart
index 56188edd2..800cde2a3 100644
--- a/lib/view/src/source/sort.dart
+++ b/lib/view/src/source/sort.dart
@@ -11,6 +11,7 @@ extension ExtraEntrySortFactorView on EntrySortFactor {
EntrySortFactor.name => l10n.sortByAlbumFileName,
EntrySortFactor.rating => l10n.sortByRating,
EntrySortFactor.size => l10n.sortBySize,
+ EntrySortFactor.duration => l10n.sortByDuration,
};
}
@@ -20,6 +21,7 @@ extension ExtraEntrySortFactorView on EntrySortFactor {
EntrySortFactor.name => AIcons.name,
EntrySortFactor.rating => AIcons.rating,
EntrySortFactor.size => AIcons.size,
+ EntrySortFactor.duration => AIcons.duration,
};
}
@@ -30,6 +32,7 @@ extension ExtraEntrySortFactorView on EntrySortFactor {
EntrySortFactor.name => reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ,
EntrySortFactor.rating => reverse ? l10n.sortOrderLowestFirst : l10n.sortOrderHighestFirst,
EntrySortFactor.size => reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst,
+ EntrySortFactor.duration => reverse ? l10n.sortOrderShortestFirst : l10n.sortOrderLongestFirst,
};
}
}
diff --git a/lib/view/view.dart b/lib/view/view.dart
index 097448a81..6096c0996 100644
--- a/lib/view/view.dart
+++ b/lib/view/view.dart
@@ -2,6 +2,7 @@ export 'src/actions/chip.dart';
export 'src/actions/chip_set.dart';
export 'src/actions/entry.dart';
export 'src/actions/entry_set.dart';
+export 'src/actions/explorer.dart';
export 'src/actions/map.dart';
export 'src/actions/map_cluster.dart';
export 'src/actions/share.dart';
diff --git a/lib/widget_common.dart b/lib/widget_common.dart
index 34a8067cb..7432c4e7e 100644
--- a/lib/widget_common.dart
+++ b/lib/widget_common.dart
@@ -18,11 +18,13 @@ import 'package:flutter/services.dart';
const _widgetDrawChannel = MethodChannel('deckers.thibault/aves/widget_draw');
void widgetMainCommon(AppFlavor flavor) async {
+ debugPrint('Widget main start');
WidgetsFlutterBinding.ensureInitialized();
initPlatformServices();
await settings.init(monitorPlatformSettings: false);
await reportService.init();
+ debugPrint('Widget channel method handling setup');
_widgetDrawChannel.setMethodCallHandler((call) async {
// widget settings may be modified in a different process after channel setup
await settings.reload();
diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart
index f279c2984..8668b3a4c 100644
--- a/lib/widgets/aves_app.dart
+++ b/lib/widgets/aves_app.dart
@@ -4,7 +4,7 @@ import 'dart:math';
import 'package:aves/app_flavor.dart';
import 'package:aves/app_mode.dart';
import 'package:aves/l10n/l10n.dart';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/recent.dart';
import 'package:aves/model/settings/defaults.dart';
@@ -345,12 +345,14 @@ class _AvesAppState extends State with WidgetsBindingObserver {
child: ValueListenableBuilder(
valueListenable: _pageTransitionsBuilderNotifier,
builder: (context, pageTransitionsBuilder, child) {
+ final theme = Theme.of(context);
return Theme(
- data: Theme.of(context).copyWith(
+ data: theme.copyWith(
pageTransitionsTheme: areAnimationsEnabled
? PageTransitionsTheme(builders: {TargetPlatform.android: pageTransitionsBuilder})
// strip page transitions used by `MaterialPageRoute`
: const DirectPageTransitionsTheme(),
+ splashFactory: areAnimationsEnabled ? theme.splashFactory : NoSplash.splashFactory,
),
child: MediaQueryDataProvider(child: child!),
);
@@ -411,6 +413,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
void didHaveMemoryPressure() {
super.didHaveMemoryPressure();
reportService.log('App memory pressure');
+ imageCache.clear();
}
@override
@@ -631,7 +634,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
final shouldReset = _exitedMainByPop;
_exitedMainByPop = false;
- if (!shouldReset && (intentData ?? {}).isEmpty) {
+ if (!shouldReset && (intentData ?? {}).values.whereNotNull().isEmpty) {
reportService.log('Relaunch');
return;
}
@@ -665,6 +668,9 @@ class _AvesAppState extends State with WidgetsBindingObserver {
}
}
+// Flutter has various overscroll indicator implementations for Android:
+// - `StretchingOverscrollIndicator`, default when using Material 3
+// - `GlowingOverscrollIndicator`, default when not using Material 3
class AvesScrollBehavior extends MaterialScrollBehavior {
@override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
@@ -674,11 +680,7 @@ class AvesScrollBehavior extends MaterialScrollBehavior {
axisDirection: details.direction,
child: child,
)
- : GlowingOverscrollIndicator(
- axisDirection: details.direction,
- color: Colors.white,
- child: child,
- );
+ : child;
}
}
diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart
index 3efb2070f..55a1f56ec 100644
--- a/lib/widgets/collection/app_bar.dart
+++ b/lib/widgets/collection/app_bar.dart
@@ -79,6 +79,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
EntrySortFactor.size,
EntrySortFactor.name,
EntrySortFactor.rating,
+ EntrySortFactor.duration,
];
static const _groupOptions = [
@@ -94,6 +95,11 @@ class _CollectionAppBarState extends State with SingleTickerPr
TileLayout.list,
];
+ static const _trashSelectionQuickActions = [
+ EntrySetAction.delete,
+ EntrySetAction.restore,
+ ];
+
@override
void initState() {
super.initState();
@@ -388,7 +394,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
final hasSelection = selectedItemCount > 0;
final browsingQuickActions = settings.collectionBrowsingQuickActions;
- final selectionQuickActions = isTrash ? [EntrySetAction.delete, EntrySetAction.restore] : settings.collectionSelectionQuickActions;
+ final selectionQuickActions = isTrash ? _trashSelectionQuickActions : settings.collectionSelectionQuickActions;
final quickActions = (isSelecting ? selectionQuickActions : browsingQuickActions).take(max(0, availableCount - 1)).toList();
final quickActionButtons = quickActions.where(isVisible).map(
(action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection),
@@ -430,7 +436,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
title: context.l10n.collectionActionEdit,
items: [
_buildRotateAndFlipMenuItems(context, canApply: canApply),
- ...EntrySetActions.edit.where((v) => isVisible(v) && !selectionQuickActions.contains(v)).map((action) => _toMenuItem(action, enabled: canApply(action), selection: selection)),
+ ...EntrySetActions.edit.where((v) => isVisible(v) && !quickActions.contains(v)).map((action) => _toMenuItem(action, enabled: canApply(action), selection: selection)),
],
),
];
diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart
index d4d54012f..e7f2ecfd3 100644
--- a/lib/widgets/collection/collection_grid.dart
+++ b/lib/widgets/collection/collection_grid.dart
@@ -697,6 +697,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
addAlbums(collection, sectionLayouts, crumbs);
case EntrySortFactor.rating:
case EntrySortFactor.size:
+ case EntrySortFactor.duration:
break;
}
return crumbs;
diff --git a/lib/widgets/collection/draggable_thumb_label.dart b/lib/widgets/collection/draggable_thumb_label.dart
index a48a6dadb..548dad5bd 100644
--- a/lib/widgets/collection/draggable_thumb_label.dart
+++ b/lib/widgets/collection/draggable_thumb_label.dart
@@ -57,6 +57,10 @@ class CollectionDraggableThumbLabel extends StatelessWidget {
return [
if (entry.sizeBytes != null) formatFileSize(context.locale, entry.sizeBytes!, round: 0),
];
+ case EntrySortFactor.duration:
+ return [
+ if (entry.durationMillis != null) entry.durationText,
+ ];
}
},
);
diff --git a/lib/widgets/collection/filter_bar.dart b/lib/widgets/collection/filter_bar.dart
index 013a39601..a88ff1be9 100644
--- a/lib/widgets/collection/filter_bar.dart
+++ b/lib/widgets/collection/filter_bar.dart
@@ -1,8 +1,10 @@
import 'package:aves/model/filters/filters.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/identity/aves_app_bar.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
class FilterBar extends StatefulWidget {
static const EdgeInsets chipPadding = EdgeInsets.symmetric(horizontal: 4);
@@ -12,7 +14,7 @@ class FilterBar extends StatefulWidget {
final List filters;
final bool interactive;
- final FilterCallback? onTap, onRemove;
+ final AFilterCallback? onTap, onRemove;
FilterBar({
super.key,
@@ -45,7 +47,7 @@ class _FilterBarState extends State {
existing.removeAt(index);
// only animate item removal when triggered by a user interaction with the chip,
// not from automatic chip replacement following chip selection
- final animate = _userTappedFilter == filter;
+ final animate = context.read().animate && _userTappedFilter == filter;
listState!.removeItem(
index,
animate
@@ -123,7 +125,7 @@ class _FilterBarState extends State {
class _Chip extends StatelessWidget {
final CollectionFilter filter;
final bool single, interactive;
- final FilterCallback? onTap, onRemove;
+ final AFilterCallback? onTap, onRemove;
const _Chip({
required this.filter,
@@ -142,7 +144,7 @@ class _Chip extends StatelessWidget {
key: ValueKey(filter),
filter: filter,
maxWidth: single
- ? AvesFilterChip.computeMaxWidth(
+ ? AvesFilterChip.computeMaxWidthForRow(
context,
minChipPerRow: 1,
chipPadding: FilterBar.chipPadding.horizontal,
diff --git a/lib/widgets/collection/grid/headers/any.dart b/lib/widgets/collection/grid/headers/any.dart
index f2f423829..213b7acf8 100644
--- a/lib/widgets/collection/grid/headers/any.dart
+++ b/lib/widgets/collection/grid/headers/any.dart
@@ -66,6 +66,7 @@ class CollectionSectionHeader extends StatelessWidget {
selectable: selectable,
);
case EntrySortFactor.size:
+ case EntrySortFactor.duration:
break;
}
return null;
diff --git a/lib/widgets/common/action_controls/quick_choosers/album_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/album_chooser.dart
index f1b5b7ee5..e705d7670 100644
--- a/lib/widgets/common/action_controls/quick_choosers/album_chooser.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/album_chooser.dart
@@ -1,14 +1,17 @@
import 'dart:async';
import 'package:aves/model/filters/album.dart';
+import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart';
-import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
+import 'package:aves/widgets/common/action_controls/quick_choosers/filter_quick_chooser_mixin.dart';
+import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
-class AlbumQuickChooser extends StatelessWidget {
+class AlbumQuickChooser extends StatelessWidget with FilterQuickChooserMixin {
final ValueNotifier valueNotifier;
+ @override
final List options;
final bool blurred;
final PopupMenuPosition chooserPosition;
@@ -25,7 +28,6 @@ class AlbumQuickChooser extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final source = context.read();
return MenuQuickChooser(
valueNotifier: valueNotifier,
options: options,
@@ -33,10 +35,17 @@ class AlbumQuickChooser extends StatelessWidget {
blurred: blurred,
chooserPosition: chooserPosition,
pointerGlobalPosition: pointerGlobalPosition,
- itemBuilder: (context, album) => AvesFilterChip(
- filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
- allowGenericIcon: false,
- ),
+ maxTotalOptionCount: FilterQuickChooserMixin.maxTotalOptionCount,
+ itemHeight: computeItemHeight(context),
+ contentWidth: computeLargestItemWidth,
+ itemBuilder: itemBuilder,
+ emptyBuilder: (context) => Text(context.l10n.albumEmpty),
);
}
+
+ @override
+ CollectionFilter buildFilter(BuildContext context, String option) {
+ final source = context.read();
+ return AlbumFilter(option, source.getAlbumDisplayName(context, option));
+ }
}
diff --git a/lib/widgets/common/action_controls/quick_choosers/common/menu.dart b/lib/widgets/common/action_controls/quick_choosers/common/menu.dart
index aff175503..b0c1a8d36 100644
--- a/lib/widgets/common/action_controls/quick_choosers/common/menu.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/common/menu.dart
@@ -1,9 +1,10 @@
import 'dart:async';
+import 'dart:math';
import 'package:aves/theme/durations.dart';
+import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart';
import 'package:aves_ui/aves_ui.dart';
-import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
@@ -16,9 +17,13 @@ class MenuQuickChooser extends StatefulWidget {
final bool blurred;
final PopupMenuPosition chooserPosition;
final Stream pointerGlobalPosition;
+ final int maxTotalOptionCount;
+ final double itemHeight;
+ final double? Function(BuildContext context)? contentWidth;
final Widget Function(BuildContext context, T menuItem) itemBuilder;
+ final WidgetBuilder? emptyBuilder;
- static const int maxOptionCount = 5;
+ static const int maxVisibleOptionCount = 5;
MenuQuickChooser({
super.key,
@@ -28,8 +33,12 @@ class MenuQuickChooser extends StatefulWidget {
required this.blurred,
required this.chooserPosition,
required this.pointerGlobalPosition,
+ this.maxTotalOptionCount = maxVisibleOptionCount,
+ this.itemHeight = kMinInteractiveDimension,
+ this.contentWidth,
required this.itemBuilder,
- }) : options = options.take(maxOptionCount).toList();
+ this.emptyBuilder,
+ }) : options = options.take(maxTotalOptionCount).toList();
@override
State> createState() => _MenuQuickChooserState();
@@ -38,6 +47,10 @@ class MenuQuickChooser extends StatefulWidget {
class _MenuQuickChooserState extends State> {
final List _subscriptions = [];
final ValueNotifier _selectedRowRect = ValueNotifier(Rect.zero);
+ final ScrollController _scrollController = ScrollController();
+ int _scrollDirection = 0;
+ Timer? _scrollUpdateTimer;
+ Offset _globalPosition = Offset.zero;
ValueNotifier get valueNotifier => widget.valueNotifier;
@@ -45,12 +58,26 @@ class _MenuQuickChooserState extends State> {
bool get reversed => widget.autoReverse && widget.chooserPosition == PopupMenuPosition.over;
- static const double intraPadding = 8;
+ bool get scrollable => options.length > MenuQuickChooser.maxVisibleOptionCount;
+
+ int get visibleOptionCount => min(MenuQuickChooser.maxVisibleOptionCount, options.length);
+
+ double get itemHeight => widget.itemHeight;
+
+ double get contentHeight => max(0, itemHeight * visibleOptionCount + _intraPadding * (visibleOptionCount - 1));
+
+ static const double _selectorMargin = 24;
+ static const double _intraPadding = 8;
+ static const double _nonScrollablePaddingHeight = _intraPadding;
+ static const double _scrollerAreaHeight = kMinInteractiveDimension;
+ static const double scrollMaxPixelPerSecond = 600.0;
+ static const Duration scrollUpdateInterval = Duration(milliseconds: 100);
@override
void initState() {
super.initState();
_registerWidget(widget);
+ WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
}
@override
@@ -69,6 +96,9 @@ class _MenuQuickChooserState extends State> {
@override
void dispose() {
_unregisterWidget(widget);
+ _selectedRowRect.dispose();
+ _scrollController.dispose();
+ _scrollUpdateTimer?.cancel();
super.dispose();
}
@@ -91,28 +121,11 @@ class _MenuQuickChooserState extends State> {
builder: (context, selectedValue, child) {
final durations = context.watch();
- List optionChildren = options.mapIndexed((index, value) {
- final isFirst = index == (reversed ? options.length - 1 : 0);
+ if (options.isEmpty) {
return Padding(
- padding: EdgeInsets.only(top: isFirst ? intraPadding : 0, bottom: intraPadding),
- child: widget.itemBuilder(context, value),
+ padding: const EdgeInsets.all(16),
+ child: widget.emptyBuilder?.call(context) ?? const SizedBox(),
);
- }).toList();
-
- optionChildren = AnimationConfiguration.toStaggeredList(
- duration: durations.staggeredAnimation * .5,
- delay: durations.staggeredAnimationDelay * .5 * timeDilation,
- childAnimationBuilder: (child) => SlideAnimation(
- verticalOffset: 50.0 * (widget.chooserPosition == PopupMenuPosition.over ? 1 : -1),
- child: FadeInAnimation(
- child: child,
- ),
- ),
- children: optionChildren,
- );
-
- if (reversed) {
- optionChildren = optionChildren.reversed.toList();
}
return Stack(
@@ -137,12 +150,67 @@ class _MenuQuickChooserState extends State> {
return child;
},
),
- Padding(
- padding: const EdgeInsetsDirectional.only(start: 24),
+ Container(
+ width: widget.contentWidth?.call(context),
+ margin: const EdgeInsetsDirectional.only(start: _selectorMargin),
child: Column(
mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: optionChildren,
+ children: [
+ scrollable
+ ? ListenableBuilder(
+ listenable: _scrollController,
+ builder: (context, child) => Opacity(
+ opacity: canGoUp ? 1 : .5,
+ child: child,
+ ),
+ child: _buildScrollerArea(AIcons.up),
+ )
+ : const SizedBox(height: _nonScrollablePaddingHeight),
+ ConstrainedBox(
+ constraints: BoxConstraints.tightFor(height: contentHeight),
+ child: ListView.separated(
+ reverse: reversed,
+ controller: _scrollController,
+ shrinkWrap: true,
+ padding: EdgeInsets.zero,
+ itemBuilder: (context, index) {
+ final child = Container(
+ alignment: AlignmentDirectional.centerStart,
+ constraints: BoxConstraints.tightFor(height: itemHeight),
+ child: widget.itemBuilder(context, options[index]),
+ );
+ if (index < MenuQuickChooser.maxVisibleOptionCount) {
+ // only animate items visible on first render
+ return AnimationConfiguration.staggeredList(
+ position: index,
+ duration: durations.staggeredAnimation * .5,
+ delay: durations.staggeredAnimationDelay * .5 * timeDilation,
+ child: SlideAnimation(
+ verticalOffset: 50.0 * (widget.chooserPosition == PopupMenuPosition.over ? 1 : -1),
+ child: FadeInAnimation(
+ child: child,
+ ),
+ ),
+ );
+ } else {
+ return child;
+ }
+ },
+ separatorBuilder: (context, index) => const SizedBox(height: _intraPadding),
+ itemCount: options.length,
+ ),
+ ),
+ scrollable
+ ? ListenableBuilder(
+ listenable: _scrollController,
+ builder: (context, child) => Opacity(
+ opacity: canGoDown ? 1 : .5,
+ child: child,
+ ),
+ child: _buildScrollerArea(AIcons.down),
+ )
+ : const SizedBox(height: _nonScrollablePaddingHeight),
+ ],
),
),
],
@@ -152,30 +220,97 @@ class _MenuQuickChooserState extends State> {
);
}
- void _onPointerMove(Offset globalPosition) {
- final padding = QuickChooser.margin.vertical + QuickChooser.padding.vertical;
+ bool get canGoUp {
+ if (!_scrollController.hasClients) return false;
+ final position = _scrollController.position;
+ return reversed ? position.pixels < position.maxScrollExtent : 0 < position.pixels;
+ }
+
+ bool get canGoDown {
+ if (!_scrollController.hasClients) return false;
+ final position = _scrollController.position;
+ return reversed ? 0 < position.pixels : position.pixels < position.maxScrollExtent;
+ }
+
+ Widget _buildScrollerArea(IconData icon) {
+ return Container(
+ alignment: Alignment.center,
+ height: _scrollerAreaHeight,
+ margin: const EdgeInsetsDirectional.only(end: _selectorMargin),
+ child: Icon(icon),
+ );
+ }
+
+ void _onPointerMove(Offset globalPosition) {
+ _globalPosition = globalPosition;
+ final chooserBox = context.findRenderObject() as RenderBox?;
+ if (chooserBox == null) return;
- final chooserBox = context.findRenderObject() as RenderBox;
final chooserSize = chooserBox.size;
final contentWidth = chooserSize.width;
- final contentHeight = chooserSize.height - padding;
+ final chooserBoxEdgeHeight = (QuickChooser.margin.vertical + QuickChooser.padding.vertical) / 2;
- final optionCount = options.length;
- final itemHeight = (contentHeight - (optionCount + 1) * intraPadding) / optionCount;
+ final localPosition = chooserBox.globalToLocal(globalPosition);
+ final dx = localPosition.dx;
+ if (!(0 < dx && dx < contentWidth)) {
+ valueNotifier.value = null;
+ return;
+ }
- final local = chooserBox.globalToLocal(globalPosition);
- final dx = local.dx;
- final dy = local.dy - padding / 2;
+ double dy = localPosition.dy - chooserBoxEdgeHeight;
+ int scrollDirection = 0;
+ if (scrollable) {
+ dy -= _scrollerAreaHeight;
+ if (-_scrollerAreaHeight < dy && dy < 0) {
+ scrollDirection = reversed ? 1 : -1;
+ } else if (contentHeight < dy && dy < contentHeight + _scrollerAreaHeight) {
+ scrollDirection = reversed ? -1 : 1;
+ }
+ _scroll(scrollDirection);
+ } else {
+ dy -= _nonScrollablePaddingHeight;
+ }
T? selectedValue;
- if (0 < dx && dx < contentWidth && 0 < dy && dy < contentHeight) {
- final index = (optionCount * dy / contentHeight).floor();
- if (0 <= index && index < optionCount) {
- selectedValue = options[reversed ? optionCount - 1 - index : index];
- final top = index * (itemHeight + intraPadding) + intraPadding;
+ if (scrollDirection == 0 && 0 < dy && dy < contentHeight) {
+ final visibleOffset = reversed ? contentHeight - dy : dy;
+ final fullItemHeight = itemHeight + _intraPadding;
+ final scrollOffset = _scrollController.offset;
+ final index = (visibleOffset + _intraPadding + scrollOffset) ~/ (fullItemHeight);
+ if (0 <= index && index < options.length) {
+ selectedValue = options[index];
+ double fromEdge = fullItemHeight * index;
+ fromEdge += (scrollable ? _scrollerAreaHeight - scrollOffset : _nonScrollablePaddingHeight);
+ final top = reversed ? chooserSize.height - chooserBoxEdgeHeight - fromEdge - fullItemHeight : fromEdge;
_selectedRowRect.value = Rect.fromLTWH(0, top, contentWidth, itemHeight);
}
}
valueNotifier.value = selectedValue;
}
+
+ void _scroll(int scrollDirection) {
+ if (scrollDirection == _scrollDirection) return;
+ _scrollDirection = scrollDirection;
+ _scrollUpdateTimer?.cancel();
+
+ final current = _scrollController.offset;
+ if (scrollDirection == 0) {
+ _scrollController.jumpTo(current);
+ return;
+ }
+
+ final target = scrollDirection > 0 ? _scrollController.position.maxScrollExtent : .0;
+ if (target != current) {
+ final distance = target - current;
+ final millis = distance * 1000 / scrollMaxPixelPerSecond / scrollDirection;
+ _scrollController.animateTo(
+ target,
+ duration: Duration(milliseconds: millis.round()),
+ curve: Curves.linear,
+ );
+ // use a timer to update the selection, because `_onPointerMove`
+ // is not called when the pointer stays still while the view is scrolling
+ _scrollUpdateTimer = Timer.periodic(scrollUpdateInterval, (_) => _onPointerMove(_globalPosition));
+ }
+ }
}
diff --git a/lib/widgets/common/action_controls/quick_choosers/filter_quick_chooser_mixin.dart b/lib/widgets/common/action_controls/quick_choosers/filter_quick_chooser_mixin.dart
new file mode 100644
index 000000000..93c9eeb22
--- /dev/null
+++ b/lib/widgets/common/action_controls/quick_choosers/filter_quick_chooser_mixin.dart
@@ -0,0 +1,56 @@
+import 'dart:math';
+
+import 'package:aves/model/filters/filters.dart';
+import 'package:aves/model/settings/modules/app.dart';
+import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+
+mixin FilterQuickChooserMixin {
+ List get options;
+
+ static const int maxTotalOptionCount = AppSettings.recentFilterHistoryMax;
+ static const double _chipPadding = AvesFilterChip.defaultPadding;
+ static const bool _chipAllowGenericIcon = false;
+
+ CollectionFilter buildFilter(BuildContext context, T option);
+
+ Widget itemBuilder(BuildContext context, T option) {
+ return AvesFilterChip(
+ filter: buildFilter(context, option),
+ allowGenericIcon: _chipAllowGenericIcon,
+ padding: _chipPadding,
+ maxWidth: double.infinity,
+ );
+ }
+
+ double computeItemHeight(BuildContext context) => AvesFilterChip.minChipHeight;
+
+ double? computeLargestItemWidth(BuildContext context) {
+ if (options.isEmpty) return null;
+
+ final textStyle = DefaultTextStyle.of(context).style.copyWith(
+ fontSize: AvesFilterChip.fontSize,
+ );
+ final textDirection = Directionality.of(context);
+ final textScaler = MediaQuery.textScalerOf(context);
+ final iconSize = textScaler.scale(AvesFilterChip.iconSize);
+
+ return options.map((option) {
+ final filter = buildFilter(context, option);
+ final icon = filter.iconBuilder(context, iconSize, allowGenericIcon: _chipAllowGenericIcon);
+ final label = filter.getLabel(context);
+ final paragraph = RenderParagraph(
+ TextSpan(text: label, style: textStyle),
+ textDirection: textDirection,
+ textScaler: textScaler,
+ )..layout(const BoxConstraints(), parentUsesSize: true);
+ final labelWidth = paragraph.getMaxIntrinsicWidth(double.infinity);
+ double chipWidth = labelWidth + _chipPadding * 4;
+ if (icon != null) {
+ chipWidth += iconSize + _chipPadding;
+ }
+ return max(AvesFilterChip.minChipWidth, chipWidth);
+ }).reduce(max);
+ }
+}
diff --git a/lib/widgets/common/action_controls/quick_choosers/move_button.dart b/lib/widgets/common/action_controls/quick_choosers/move_button.dart
index 0d5ac1b66..f15cca1c3 100644
--- a/lib/widgets/common/action_controls/quick_choosers/move_button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/move_button.dart
@@ -5,7 +5,7 @@ import 'package:aves/model/source/collection_source.dart';
import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/album_chooser.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart';
-import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart';
+import 'package:aves/widgets/common/action_controls/quick_choosers/filter_quick_chooser_mixin.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
import 'package:aves_model/aves_model.dart';
@@ -42,7 +42,7 @@ class _MoveButtonState extends ChooserQuickButtonState {
final source = context.read();
final rawAlbums = source.rawAlbums;
final options = settings.recentDestinationAlbums.where(rawAlbums.contains).toList();
- final takeCount = MenuQuickChooser.maxOptionCount - options.length;
+ final takeCount = FilterQuickChooserMixin.maxTotalOptionCount - options.length;
if (takeCount > 0) {
final filters = rawAlbums.whereNot(options.contains).map((album) => AlbumFilter(album, null)).toSet();
final allMapEntries = filters.map((filter) => FilterGridItem(filter, source.recentEntry(filter))).toList();
diff --git a/lib/widgets/common/action_controls/quick_choosers/share_button.dart b/lib/widgets/common/action_controls/quick_choosers/share_button.dart
index 009594358..df22d7b17 100644
--- a/lib/widgets/common/action_controls/quick_choosers/share_button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/share_button.dart
@@ -51,7 +51,6 @@ class _ShareButtonState extends ChooserQuickButtonState valueNotifier;
final List options;
- final bool autoReverse;
final bool blurred;
final PopupMenuPosition chooserPosition;
final Stream pointerGlobalPosition;
+ static const _itemPadding = EdgeInsetsDirectional.only(end: 8);
+
const ShareQuickChooser({
super.key,
required this.valueNotifier,
required this.options,
- required this.autoReverse,
required this.blurred,
required this.chooserPosition,
required this.pointerGlobalPosition,
@@ -29,20 +31,39 @@ class ShareQuickChooser extends StatelessWidget {
return MenuQuickChooser(
valueNotifier: valueNotifier,
options: options,
- autoReverse: autoReverse,
+ autoReverse: false,
blurred: blurred,
chooserPosition: chooserPosition,
pointerGlobalPosition: pointerGlobalPosition,
- itemBuilder: (context, action) => ConstrainedBox(
- constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
- child: Padding(
- padding: const EdgeInsetsDirectional.only(end: 8),
- child: MenuRow(
- text: action.getText(context),
- icon: action.getIcon(),
- ),
+ itemHeight: kMinInteractiveDimension,
+ contentWidth: _computeLargestItemWidth,
+ itemBuilder: (context, action) => Padding(
+ padding: _itemPadding,
+ child: MenuRow(
+ text: action.getText(context),
+ icon: action.getIcon(),
),
),
);
}
+
+ double? _computeLargestItemWidth(BuildContext context) {
+ if (options.isEmpty) return null;
+
+ final textStyle = DefaultTextStyle.of(context).style;
+ final textDirection = Directionality.of(context);
+ final textScaler = MediaQuery.textScalerOf(context);
+ final iconSize = IconTheme.of(context).size ?? 24;
+
+ return options.map((action) {
+ final text = action.getText(context);
+ final paragraph = RenderParagraph(
+ TextSpan(text: text, style: textStyle),
+ textDirection: textDirection,
+ textScaler: textScaler,
+ )..layout(const BoxConstraints(), parentUsesSize: true);
+ final labelWidth = paragraph.getMaxIntrinsicWidth(double.infinity);
+ return iconSize + MenuRow.leadingPadding.horizontal + labelWidth + _itemPadding.horizontal;
+ }).reduce(max);
+ }
}
diff --git a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart
index 565773837..9437c1f24 100644
--- a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart
@@ -4,7 +4,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart';
-import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart';
+import 'package:aves/widgets/common/action_controls/quick_choosers/filter_quick_chooser_mixin.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/tag_chooser.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
@@ -38,7 +38,7 @@ class _TagButtonState extends ChooserQuickButtonState animation, PopupMenuPosition chooserPosition) {
final options = settings.recentTags;
- final takeCount = MenuQuickChooser.maxOptionCount - options.length;
+ final takeCount = FilterQuickChooserMixin.maxTotalOptionCount - options.length;
if (takeCount > 0) {
final source = context.read();
final filters = source.sortedTags.map(TagFilter.new).whereNot(options.contains).toSet();
diff --git a/lib/widgets/common/action_controls/quick_choosers/tag_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/tag_chooser.dart
index 1947814f2..1b139e507 100644
--- a/lib/widgets/common/action_controls/quick_choosers/tag_chooser.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/tag_chooser.dart
@@ -2,11 +2,13 @@ import 'dart:async';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart';
-import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
+import 'package:aves/widgets/common/action_controls/quick_choosers/filter_quick_chooser_mixin.dart';
+import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/material.dart';
-class TagQuickChooser extends StatelessWidget {
+class TagQuickChooser extends StatelessWidget with FilterQuickChooserMixin {
final ValueNotifier valueNotifier;
+ @override
final List options;
final bool blurred;
final PopupMenuPosition chooserPosition;
@@ -30,10 +32,14 @@ class TagQuickChooser extends StatelessWidget {
blurred: blurred,
chooserPosition: chooserPosition,
pointerGlobalPosition: pointerGlobalPosition,
- itemBuilder: (context, filter) => AvesFilterChip(
- filter: filter,
- allowGenericIcon: false,
- ),
+ maxTotalOptionCount: FilterQuickChooserMixin.maxTotalOptionCount,
+ itemHeight: computeItemHeight(context),
+ contentWidth: computeLargestItemWidth,
+ itemBuilder: itemBuilder,
+ emptyBuilder: (context) => Text(context.l10n.tagEmpty),
);
}
+
+ @override
+ CollectionFilter buildFilter(BuildContext context, CollectionFilter option) => option;
}
diff --git a/lib/widgets/common/action_controls/togglers/favourite.dart b/lib/widgets/common/action_controls/togglers/favourite.dart
index 31b0bbd79..ea683b0a6 100644
--- a/lib/widgets/common/action_controls/togglers/favourite.dart
+++ b/lib/widgets/common/action_controls/togglers/favourite.dart
@@ -1,6 +1,7 @@
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/favourites.dart';
import 'package:aves/model/favourites.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
@@ -73,6 +74,7 @@ class _FavouriteTogglerState extends State {
icon: const Icon(isNotFavouriteIcon),
);
}
+ final animate = context.select((v) => v.animate);
return Stack(
alignment: Alignment.center,
children: [
@@ -82,14 +84,15 @@ class _FavouriteTogglerState extends State {
focusNode: widget.focusNode,
tooltip: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite,
),
- Sweeper(
- key: ValueKey(entries.length == 1 ? entries.first : entries.length),
- builder: (context) => Icon(
- favouriteSweeperIcon,
- color: context.select((v) => v.favourite),
+ if (animate)
+ Sweeper(
+ key: ValueKey(entries.length == 1 ? entries.first : entries.length),
+ builder: (context) => Icon(
+ favouriteSweeperIcon,
+ color: context.select((v) => v.favourite),
+ ),
+ toggledNotifier: _isFavouriteNotifier,
),
- toggledNotifier: _isFavouriteNotifier,
- ),
],
);
},
diff --git a/lib/widgets/common/action_controls/togglers/play.dart b/lib/widgets/common/action_controls/togglers/play.dart
index 7ca45ffe3..9e76d2d13 100644
--- a/lib/widgets/common/action_controls/togglers/play.dart
+++ b/lib/widgets/common/action_controls/togglers/play.dart
@@ -95,9 +95,9 @@ class _PlayTogglerState extends State with SingleTickerProviderStat
void _onStatusChanged(VideoStatus status) {
final status = _playPauseAnimation.status;
- if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) {
+ if (isPlaying && !status.isForwardOrCompleted) {
_playPauseAnimation.forward();
- } else if (!isPlaying && status != AnimationStatus.reverse && status != AnimationStatus.dismissed) {
+ } else if (!isPlaying && status.isForwardOrCompleted) {
_playPauseAnimation.reverse();
}
}
diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart
index cf29fccaf..a8ac02206 100644
--- a/lib/widgets/common/action_mixins/feedback.dart
+++ b/lib/widgets/common/action_mixins/feedback.dart
@@ -24,6 +24,7 @@ typedef MarginComputer = EdgeInsets Function(BuildContext context);
mixin FeedbackMixin {
static final ValueNotifier snackBarMarginOverrideNotifier = ValueNotifier(null);
+ static OverlaySupportEntry? _overlayNotificationEntry;
static EdgeInsets snackBarMarginDefault(BuildContext context) {
return EdgeInsets.only(
@@ -31,7 +32,11 @@ mixin FeedbackMixin {
);
}
- void dismissFeedback(BuildContext context) => ScaffoldMessenger.of(context).hideCurrentSnackBar();
+ void dismissFeedback(BuildContext context) {
+ ScaffoldMessenger.of(context).hideCurrentSnackBar();
+ _overlayNotificationEntry?.dismiss();
+ _overlayNotificationEntry = null;
+ }
void showFeedback(BuildContext context, FeedbackType type, String message, [SnackBarAction? action]) {
ScaffoldMessengerState? scaffoldMessenger;
@@ -67,8 +72,7 @@ mixin FeedbackMixin {
// and space under the snack bar `margin` does not receive gestures
// (because it is used by the `Dismissible` wrapping the snack bar)
// so we use `showOverlayNotification` instead
- OverlaySupportEntry? notificationOverlayEntry;
- notificationOverlayEntry = showOverlayNotification(
+ _overlayNotificationEntry = showOverlayNotification(
(context) => SafeArea(
bottom: false,
child: ValueListenableBuilder(
@@ -89,7 +93,7 @@ mixin FeedbackMixin {
foregroundColor: WidgetStateProperty.all(snackBarTheme.actionTextColor),
),
onPressed: () {
- notificationOverlayEntry?.dismiss();
+ dismissFeedback(context);
action.onPressed();
},
child: Text(action.label),
@@ -97,7 +101,7 @@ mixin FeedbackMixin {
: null,
animation: kAlwaysCompleteAnimation,
dismissDirection: DismissDirection.horizontal,
- onDismiss: () => notificationOverlayEntry?.dismiss(),
+ onDismiss: () => dismissFeedback(context),
),
),
),
diff --git a/lib/widgets/common/action_mixins/overlay_snack_bar.dart b/lib/widgets/common/action_mixins/overlay_snack_bar.dart
index ecd88f143..a32206a29 100644
--- a/lib/widgets/common/action_mixins/overlay_snack_bar.dart
+++ b/lib/widgets/common/action_mixins/overlay_snack_bar.dart
@@ -135,8 +135,8 @@ class _OverlaySnackBarState extends State {
super.dispose();
}
- void _onAnimationStatusChanged(AnimationStatus animationStatus) {
- if (animationStatus == AnimationStatus.completed) {
+ void _onAnimationStatusChanged(AnimationStatus status) {
+ if (status.isCompleted) {
if (widget.onVisible != null && !_wasVisible) {
widget.onVisible!();
}
diff --git a/lib/widgets/common/basic/popup/menu_button.dart b/lib/widgets/common/basic/popup/menu_button.dart
deleted file mode 100644
index f0f3d178b..000000000
--- a/lib/widgets/common/basic/popup/menu_button.dart
+++ /dev/null
@@ -1,37 +0,0 @@
-import 'package:flutter/material.dart';
-
-class AvesPopupMenuButton extends PopupMenuButton {
- final VoidCallback? onMenuOpened;
-
- const AvesPopupMenuButton({
- super.key,
- required super.itemBuilder,
- super.initialValue,
- super.onSelected,
- super.onCanceled,
- super.tooltip,
- super.elevation,
- super.padding = const EdgeInsets.all(8),
- super.child,
- super.icon,
- super.offset = Offset.zero,
- super.enabled = true,
- super.shape,
- super.color,
- super.enableFeedback,
- super.iconSize,
- this.onMenuOpened,
- super.popUpAnimationStyle,
- });
-
- @override
- PopupMenuButtonState createState() => _AvesPopupMenuButtonState();
-}
-
-class _AvesPopupMenuButtonState extends PopupMenuButtonState {
- @override
- void showButtonMenu() {
- (widget as AvesPopupMenuButton).onMenuOpened?.call();
- super.showButtonMenu();
- }
-}
diff --git a/lib/widgets/common/basic/popup/menu_row.dart b/lib/widgets/common/basic/popup/menu_row.dart
index 33773c04f..23f77b47e 100644
--- a/lib/widgets/common/basic/popup/menu_row.dart
+++ b/lib/widgets/common/basic/popup/menu_row.dart
@@ -10,6 +10,8 @@ class MenuRow extends StatelessWidget {
this.icon,
});
+ static const leadingPadding = EdgeInsetsDirectional.only(end: 12);
+
@override
Widget build(BuildContext context) {
return Row(
@@ -17,7 +19,7 @@ class MenuRow extends StatelessWidget {
children: [
if (icon != null)
Padding(
- padding: const EdgeInsetsDirectional.only(end: 12),
+ padding: leadingPadding,
child: IconTheme.merge(
data: IconThemeData(
color: ListTileTheme.of(context).iconColor,
diff --git a/lib/widgets/common/behaviour/pop/scope.dart b/lib/widgets/common/behaviour/pop/scope.dart
index f75ca980e..ff750b9ae 100644
--- a/lib/widgets/common/behaviour/pop/scope.dart
+++ b/lib/widgets/common/behaviour/pop/scope.dart
@@ -17,7 +17,7 @@ class AvesPopScope extends StatelessWidget {
final blocker = handlers.firstWhereOrNull((v) => !v.canPop(context));
return PopScope(
canPop: blocker == null,
- onPopInvoked: (didPop) {
+ onPopInvokedWithResult: (didPop, result) {
if (!didPop) {
blocker?.onPopBlocked(context);
}
diff --git a/lib/widgets/common/expandable_filter_row.dart b/lib/widgets/common/expandable_filter_row.dart
index f96cdf7e0..a7111d555 100644
--- a/lib/widgets/common/expandable_filter_row.dart
+++ b/lib/widgets/common/expandable_filter_row.dart
@@ -12,7 +12,7 @@ class TitledExpandableFilterRow extends StatelessWidget {
final ValueNotifier expandedNotifier;
final bool showGenericIcon;
final HeroType Function(CollectionFilter filter)? heroTypeBuilder;
- final FilterCallback onTap;
+ final AFilterCallback onTap;
final OffsetFilterCallback? onLongPress;
const TitledExpandableFilterRow({
@@ -96,8 +96,8 @@ class ExpandableFilterRow extends StatelessWidget {
final bool showGenericIcon;
final Widget? Function(CollectionFilter)? leadingBuilder;
final HeroType Function(CollectionFilter filter)? heroTypeBuilder;
- final FilterCallback onTap;
- final FilterCallback? onRemove;
+ final AFilterCallback onTap;
+ final AFilterCallback? onRemove;
final OffsetFilterCallback? onLongPress;
static const double horizontalPadding = 8;
diff --git a/lib/widgets/common/fx/sweeper.dart b/lib/widgets/common/fx/sweeper.dart
index 6ed6bdec8..43e64d2e4 100644
--- a/lib/widgets/common/fx/sweeper.dart
+++ b/lib/widgets/common/fx/sweeper.dart
@@ -105,7 +105,7 @@ class _SweeperState extends State with SingleTickerProviderStateMixin {
void _onAnimationStatusChanged(AnimationStatus status) {
setState(() {});
- if (status == AnimationStatus.completed) {
+ if (status.isCompleted) {
widget.onSweepEnd?.call();
}
}
diff --git a/lib/widgets/common/grid/header.dart b/lib/widgets/common/grid/header.dart
index b1efa5884..0b60f13c0 100644
--- a/lib/widgets/common/grid/header.dart
+++ b/lib/widgets/common/grid/header.dart
@@ -189,6 +189,7 @@ class _SectionSelectableLeading extends StatelessWidget {
Widget build(BuildContext context) {
if (!selectable) return _buildBrowsing(context);
+ final duration = context.select((v) => v.formTransition);
final isSelecting = context.select, bool>((selection) => selection.isSelecting);
final Widget child = isSelecting
? _SectionSelectingLeading(
@@ -201,7 +202,7 @@ class _SectionSelectableLeading extends StatelessWidget {
descendantsAreFocusable: false,
descendantsAreTraversable: false,
child: AnimatedSwitcher(
- duration: ADurations.sectionHeaderAnimation,
+ duration: duration,
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeInOut,
transitionBuilder: (child, animation) {
@@ -240,11 +241,12 @@ class _SectionSelectingLeading extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final duration = context.select((v) => v.formTransition);
final sectionEntries = context.watch>().sections[sectionKey] ?? [];
final selection = context.watch>();
final isSelected = selection.isSelected(sectionEntries);
return AnimatedSwitcher(
- duration: ADurations.sectionHeaderAnimation,
+ duration: duration,
switchInCurve: Curves.easeOutBack,
switchOutCurve: Curves.easeOutBack,
transitionBuilder: (child, animation) => ScaleTransition(
diff --git a/lib/widgets/common/grid/overlay.dart b/lib/widgets/common/grid/overlay.dart
index f50b1a11c..ed53d9a12 100644
--- a/lib/widgets/common/grid/overlay.dart
+++ b/lib/widgets/common/grid/overlay.dart
@@ -10,8 +10,6 @@ class GridItemSelectionOverlay extends StatelessWidget {
final BorderRadius? borderRadius;
final EdgeInsets? padding;
- static const duration = ADurations.thumbnailOverlayAnimation;
-
const GridItemSelectionOverlay({
super.key,
required this.item,
@@ -21,6 +19,7 @@ class GridItemSelectionOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final duration = context.select((v) => v.formTransition);
final isSelecting = context.select, bool>((selection) => selection.isSelecting);
return AnimatedSwitcher(
duration: duration,
diff --git a/lib/widgets/common/grid/selector.dart b/lib/widgets/common/grid/selector.dart
index dc7058347..45aa02cab 100644
--- a/lib/widgets/common/grid/selector.dart
+++ b/lib/widgets/common/grid/selector.dart
@@ -55,9 +55,9 @@ class _GridSelectionGestureDetectorState extends State extends State extends State 0 ? scrollController.position.maxScrollExtent : .0;
if (target != current) {
final distance = target - current;
- final millis = distance * 1000 / scrollMaxPixelPerSecond / speedFactor;
+ final millis = distance * 1000 / _scrollMaxPixelPerSecond / speedFactor;
scrollController.animateTo(
target,
duration: Duration(milliseconds: millis.round()),
@@ -193,7 +193,7 @@ class _GridSelectionGestureDetectorState extends State _onLongPressUpdate());
+ _selectionUpdateTimer = Timer.periodic(_scrollUpdateInterval, (_) => _onLongPressUpdate());
}
}
diff --git a/lib/widgets/common/identity/aves_app_bar.dart b/lib/widgets/common/identity/aves_app_bar.dart
index d0d7e6ba9..38c19167d 100644
--- a/lib/widgets/common/identity/aves_app_bar.dart
+++ b/lib/widgets/common/identity/aves_app_bar.dart
@@ -40,6 +40,50 @@ class AvesAppBar extends StatelessWidget {
final colorScheme = theme.colorScheme;
final textScaler = MediaQuery.textScalerOf(context);
final useTvLayout = settings.useTvLayout;
+
+ Widget? _leading = leading;
+ if (_leading != null) {
+ _leading = FontSizeIconTheme(
+ child: _leading,
+ );
+ }
+
+ Widget _title = FontSizeIconTheme(
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return Row(
+ key: ValueKey(transitionKey),
+ children: [
+ Expanded(child: title),
+ ...(actions(context, max(0, constraints.maxWidth - _titleMinWidth))),
+ ],
+ );
+ },
+ ),
+ );
+
+ final animate = context.select((v) => v.animate);
+ if (animate) {
+ _title = Hero(
+ tag: titleHeroTag,
+ flightShuttleBuilder: _flightShuttleBuilder,
+ transitionOnUserGestures: true,
+ child: AnimatedSwitcher(
+ duration: context.read().iconAnimation,
+ child: _title,
+ ),
+ );
+
+ if (_leading != null) {
+ _leading = Hero(
+ tag: leadingHeroTag,
+ flightShuttleBuilder: _flightShuttleBuilder,
+ transitionOnUserGestures: true,
+ child: _leading,
+ );
+ }
+ }
+
return SliverPersistentHeader(
floating: !useTvLayout,
pinned: pinned,
@@ -70,43 +114,16 @@ class AvesAppBar extends StatelessWidget {
height: textScaler.scale(kToolbarHeight),
child: Row(
children: [
- leading != null
+ _leading != null
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
- child: Hero(
- tag: leadingHeroTag,
- flightShuttleBuilder: _flightShuttleBuilder,
- transitionOnUserGestures: true,
- child: FontSizeIconTheme(
- child: leading!,
- ),
- ),
+ child: _leading,
)
: const SizedBox(width: 16),
Expanded(
child: DefaultTextStyle(
style: theme.appBarTheme.titleTextStyle!,
- child: Hero(
- tag: titleHeroTag,
- flightShuttleBuilder: _flightShuttleBuilder,
- transitionOnUserGestures: true,
- child: AnimatedSwitcher(
- duration: context.read().iconAnimation,
- child: FontSizeIconTheme(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return Row(
- key: ValueKey(transitionKey),
- children: [
- Expanded(child: title),
- ...(actions(context, max(0, constraints.maxWidth - _titleMinWidth))),
- ],
- );
- },
- ),
- ),
- ),
- ),
+ child: _title,
),
),
],
diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart
index def2ceaf6..9f0108004 100644
--- a/lib/widgets/common/identity/aves_filter_chip.dart
+++ b/lib/widgets/common/identity/aves_filter_chip.dart
@@ -26,7 +26,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
-typedef FilterCallback = void Function(CollectionFilter filter);
+typedef AFilterCallback = void Function(CollectionFilter filter);
typedef OffsetFilterCallback = void Function(BuildContext context, CollectionFilter filter, Offset tapPosition);
enum HeroType { always, onTap, never }
@@ -56,9 +56,10 @@ class AvesFilterChip extends StatefulWidget {
final double padding;
final double? maxWidth;
final HeroType heroType;
- final FilterCallback? onTap, onRemove;
+ final AFilterCallback? onTap, onRemove;
final OffsetFilterCallback? onLongPress;
+ static const double defaultPadding = 6.0;
static const double defaultRadius = 32;
static const double outlineWidth = 2;
static const double minChipHeight = kMinInteractiveDimension;
@@ -79,7 +80,7 @@ class AvesFilterChip extends StatefulWidget {
this.banner,
this.leadingOverride,
this.details,
- this.padding = 6.0,
+ this.padding = defaultPadding,
this.maxWidth,
this.heroType = HeroType.onTap,
this.onTap,
@@ -87,7 +88,7 @@ class AvesFilterChip extends StatefulWidget {
this.onLongPress = showDefaultLongPressMenu,
});
- static double computeMaxWidth(
+ static double computeMaxWidthForRow(
BuildContext context, {
required int minChipPerRow,
required double chipPadding,
@@ -347,7 +348,7 @@ class _AvesFilterChipState extends State {
maxWidth: max(
AvesFilterChip.minChipWidth,
widget.maxWidth ??
- AvesFilterChip.computeMaxWidth(
+ AvesFilterChip.computeMaxWidthForRow(
context,
minChipPerRow: 2,
chipPadding: FilterBar.chipPadding.horizontal,
diff --git a/lib/widgets/common/map/buttons/panel.dart b/lib/widgets/common/map/buttons/panel.dart
index c25427800..f329bbb6d 100644
--- a/lib/widgets/common/map/buttons/panel.dart
+++ b/lib/widgets/common/map/buttons/panel.dart
@@ -124,7 +124,13 @@ class MapButtonPanel extends StatelessWidget {
Padding(
padding: EdgeInsets.only(top: padding),
// key is expected by test driver
- child: _buildButton(context, MapAction.selectStyle, buttonKey: const Key('map-menu-layers')),
+ child: Column(
+ children: [
+ _buildButton(context, MapAction.selectStyle, buttonKey: const Key('map-menu-layers')),
+ SizedBox(height: padding),
+ _buildButton(context, MapAction.openMapApp),
+ ],
+ ),
),
],
),
diff --git a/lib/widgets/common/map/leaflet/map.dart b/lib/widgets/common/map/leaflet/map.dart
index 84d87be05..cca10254c 100644
--- a/lib/widgets/common/map/leaflet/map.dart
+++ b/lib/widgets/common/map/leaflet/map.dart
@@ -286,7 +286,7 @@ class _EntryLeafletMapState extends State> with TickerProv
final animation = CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
controller.addListener(() => animate(animation));
animation.addStatusListener((status) {
- if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
+ if (!status.isAnimating) {
animation.dispose();
controller.dispose();
}
diff --git a/lib/widgets/common/map/map_action_delegate.dart b/lib/widgets/common/map/map_action_delegate.dart
index d5fe1b079..5c3728e05 100644
--- a/lib/widgets/common/map/map_action_delegate.dart
+++ b/lib/widgets/common/map/map_action_delegate.dart
@@ -26,6 +26,8 @@ class MapActionDelegate {
),
onSelection: (v) => settings.mapStyle = v,
);
+ case MapAction.openMapApp:
+ OpenMapAppNotification().dispatch(context);
case MapAction.zoomIn:
controller?.zoomBy(1);
case MapAction.zoomOut:
@@ -33,3 +35,5 @@ class MapActionDelegate {
}
}
}
+
+class OpenMapAppNotification extends Notification {}
diff --git a/lib/widgets/common/search/delegate.dart b/lib/widgets/common/search/delegate.dart
index edd04390b..14d6a5a81 100644
--- a/lib/widgets/common/search/delegate.dart
+++ b/lib/widgets/common/search/delegate.dart
@@ -5,6 +5,7 @@ import 'package:aves/widgets/common/search/route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:provider/provider.dart';
abstract class AvesSearchDelegate extends SearchDelegate {
final String routeName;
@@ -38,12 +39,13 @@ abstract class AvesSearchDelegate extends SearchDelegate {
// use a property instead of checking `Navigator.canPop(context)`
// because the navigator state changes as soon as we press back
// so the leading may mistakenly switch to the close button
+ final animate = context.read().animate;
return canPop
? IconButton(
- icon: AnimatedIcon(
+ icon: animate ? AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
- ),
+ ): const Icon(Icons.arrow_back),
onPressed: () => goBack(context),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
)
diff --git a/lib/widgets/common/search/page.dart b/lib/widgets/common/search/page.dart
index 6bbf50f8f..28c8d04d9 100644
--- a/lib/widgets/common/search/page.dart
+++ b/lib/widgets/common/search/page.dart
@@ -1,3 +1,4 @@
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/utils/debouncer.dart';
@@ -10,6 +11,7 @@ import 'package:aves/widgets/common/search/delegate.dart';
import 'package:aves/widgets/common/search/route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
+import 'package:provider/provider.dart';
class SearchPage extends StatefulWidget {
static const routeName = '/search';
@@ -70,7 +72,7 @@ class _SearchPageState extends State {
}
void _onAnimationStatusChanged(AnimationStatus status) {
- if (status != AnimationStatus.completed) {
+ if (!status.isCompleted) {
return;
}
widget.animation.removeStatusListener(_onAnimationStatusChanged);
@@ -103,7 +105,24 @@ class _SearchPageState extends State {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
- Widget? body;
+
+ Widget leading = Center(child: widget.delegate.buildLeading(context));
+ Widget title = DefaultTextStyle.merge(
+ style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
+ child: TextField(
+ controller: widget.delegate.queryTextController,
+ focusNode: _searchFieldFocusNode,
+ decoration: InputDecoration(
+ border: InputBorder.none,
+ hintText: widget.delegate.searchFieldLabel,
+ hintStyle: theme.inputDecorationTheme.hintStyle,
+ ),
+ textInputAction: TextInputAction.search,
+ style: Themes.searchFieldStyle(context),
+ onSubmitted: (_) => widget.delegate.showResults(context),
+ ),
+ );
+ Widget body;
switch (widget.delegate.currentBody) {
case SearchBody.suggestions:
body = KeyedSubtree(
@@ -116,34 +135,31 @@ class _SearchPageState extends State {
child: widget.delegate.buildResults(context),
);
case null:
- break;
+ body = const SizedBox();
}
+
+ final animate = context.select((v) => v.animate);
+ if (animate) {
+ leading = Hero(
+ tag: AvesAppBar.leadingHeroTag,
+ transitionOnUserGestures: true,
+ child: leading,
+ );
+ title = Hero(
+ tag: AvesAppBar.titleHeroTag,
+ transitionOnUserGestures: true,
+ child: title,
+ );
+ body = AnimatedSwitcher(
+ duration: ADurations.searchBodyTransition,
+ child: body,
+ );
+ }
+
return AvesScaffold(
appBar: AppBar(
- leading: Hero(
- tag: AvesAppBar.leadingHeroTag,
- transitionOnUserGestures: true,
- child: Center(child: widget.delegate.buildLeading(context)),
- ),
- title: Hero(
- tag: AvesAppBar.titleHeroTag,
- transitionOnUserGestures: true,
- child: DefaultTextStyle.merge(
- style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
- child: TextField(
- controller: widget.delegate.queryTextController,
- focusNode: _searchFieldFocusNode,
- decoration: InputDecoration(
- border: InputBorder.none,
- hintText: widget.delegate.searchFieldLabel,
- hintStyle: theme.inputDecorationTheme.hintStyle,
- ),
- textInputAction: TextInputAction.search,
- style: Themes.searchFieldStyle(context),
- onSubmitted: (_) => widget.delegate.showResults(context),
- ),
- ),
- ),
+ leading: leading,
+ title: title,
actions: widget.delegate.buildActions(context),
),
body: AvesPopScope(
@@ -151,10 +167,7 @@ class _SearchPageState extends State {
tvNavigationPopHandler,
doubleBackPopHandler,
],
- child: AnimatedSwitcher(
- duration: const Duration(milliseconds: 300),
- child: body,
- ),
+ child: body,
),
);
}
diff --git a/lib/widgets/common/search/route.dart b/lib/widgets/common/search/route.dart
index 8b5d15452..6813b6892 100644
--- a/lib/widgets/common/search/route.dart
+++ b/lib/widgets/common/search/route.dart
@@ -1,6 +1,8 @@
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/search/delegate.dart';
import 'package:aves/widgets/common/search/page.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
// adapted from Flutter `_SearchBody` in `/material/search.dart`
enum SearchBody { suggestions, results }
@@ -42,10 +44,13 @@ class SearchPageRoute extends PageRoute {
) {
// a simple fade is usually more fitting for a search page,
// instead of the `pageTransitionsTheme` used by the rest of the app
- return FadeTransition(
- opacity: animation,
- child: child,
- );
+ final animate = context.read().animate;
+ return animate
+ ? FadeTransition(
+ opacity: animation,
+ child: child,
+ )
+ : child;
}
@override
diff --git a/lib/widgets/common/thumbnail/overlay.dart b/lib/widgets/common/thumbnail/overlay.dart
index abbe77111..28050d13f 100644
--- a/lib/widgets/common/thumbnail/overlay.dart
+++ b/lib/widgets/common/thumbnail/overlay.dart
@@ -89,10 +89,10 @@ class ThumbnailZoomOverlay extends StatelessWidget {
});
static const alignment = AlignmentDirectional.bottomEnd;
- static const duration = ADurations.thumbnailOverlayAnimation;
@override
Widget build(BuildContext context) {
+ final duration = context.select((v) => v.formTransition);
final isSelecting = context.select, bool>((selection) => selection.isSelecting);
final interactiveDimension = context.select((t) => t.interactiveDimension);
return AnimatedSwitcher(
diff --git a/lib/widgets/debug/android_apps.dart b/lib/widgets/debug/android_apps.dart
index 708566eda..101b18c08 100644
--- a/lib/widgets/debug/android_apps.dart
+++ b/lib/widgets/debug/android_apps.dart
@@ -1,5 +1,5 @@
import 'package:aves/image_providers/app_icon_image_provider.dart';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/basic/query_bar.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
diff --git a/lib/widgets/debug/database.dart b/lib/widgets/debug/database.dart
index b8f6f5836..541d66321 100644
--- a/lib/widgets/debug/database.dart
+++ b/lib/widgets/debug/database.dart
@@ -6,7 +6,7 @@ import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata/trash.dart';
import 'package:aves/model/vaults/details.dart';
import 'package:aves/model/vaults/vaults.dart';
-import 'package:aves/model/video_playback.dart';
+import 'package:aves/model/viewer/video_playback.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/utils/file_utils.dart';
diff --git a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart
index ab9e8e7ae..28e9c7159 100644
--- a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart
+++ b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart
@@ -74,7 +74,7 @@ class _TagEditorPageState extends State {
return PopScope(
canPop: !_isModified,
- onPopInvoked: (didPop) async {
+ onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final NavigatorState navigator = Navigator.of(context);
diff --git a/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart
index f38d386a9..51635f243 100644
--- a/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart
+++ b/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart
@@ -1,5 +1,5 @@
import 'package:aves/image_providers/app_icon_image_provider.dart';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/basic/list_tiles/reselectable_radio.dart';
diff --git a/lib/widgets/editor/control_panel.dart b/lib/widgets/editor/control_panel.dart
index 531fd2848..20ef45aca 100644
--- a/lib/widgets/editor/control_panel.dart
+++ b/lib/widgets/editor/control_panel.dart
@@ -30,7 +30,7 @@ class EditorControlPanel extends StatelessWidget {
Widget build(BuildContext context) {
return PopScope(
canPop: actionNotifier.value == null,
- onPopInvoked: (didPop) {
+ onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
_cancelAction(context);
diff --git a/lib/widgets/editor/entry_editor_page.dart b/lib/widgets/editor/entry_editor_page.dart
index f375aeb27..5acce2bca 100644
--- a/lib/widgets/editor/entry_editor_page.dart
+++ b/lib/widgets/editor/entry_editor_page.dart
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/settings/settings.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/editor/control_panel.dart';
import 'package:aves/widgets/editor/image.dart';
import 'package:aves/widgets/editor/transform/controller.dart';
diff --git a/lib/widgets/editor/image.dart b/lib/widgets/editor/image.dart
index 2b2f703ea..c8b01d45f 100644
--- a/lib/widgets/editor/image.dart
+++ b/lib/widgets/editor/image.dart
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:aves/model/entry/entry.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/editor/transform/controller.dart';
import 'package:aves/widgets/editor/transform/painter.dart';
diff --git a/lib/widgets/editor/transform/cropper.dart b/lib/widgets/editor/transform/cropper.dart
index c3abc7338..310cb618e 100644
--- a/lib/widgets/editor/transform/cropper.dart
+++ b/lib/widgets/editor/transform/cropper.dart
@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:math';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/extensions/geometry.dart';
import 'package:aves/widgets/common/fx/dashed_path_painter.dart';
diff --git a/lib/widgets/explorer/app_bar.dart b/lib/widgets/explorer/app_bar.dart
index c5a996ee9..804af6a45 100644
--- a/lib/widgets/explorer/app_bar.dart
+++ b/lib/widgets/explorer/app_bar.dart
@@ -7,7 +7,6 @@ import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/utils/android_file_utils.dart';
-import 'package:aves/view/src/actions/explorer.dart';
import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart';
import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
@@ -27,7 +26,7 @@ import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class ExplorerAppBar extends StatefulWidget {
- final ValueNotifier directoryNotifier;
+ final ValueNotifier directoryNotifier;
final void Function(String path) goTo;
const ExplorerAppBar({
@@ -68,7 +67,7 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse
return SizedBox(
width: constraints.maxWidth,
height: CrumbLine.getPreferredHeight(MediaQuery.textScalerOf(context)),
- child: ValueListenableBuilder(
+ child: ValueListenableBuilder(
valueListenable: widget.directoryNotifier,
builder: (context, directory, child) {
return CrumbLine(
@@ -118,7 +117,10 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse
return [
ExplorerAction.addShortcut,
ExplorerAction.setHome,
- ].map((v) {
+ null,
+ ExplorerAction.stats,
+ ].map>((v) {
+ if (v == null) return const PopupMenuDivider();
return PopupMenuItem(
value: v,
child: MenuRow(text: v.getText(context), icon: v.getIcon()),
@@ -129,7 +131,9 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse
// wait for the popup menu to hide before proceeding with the action
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
final directory = widget.directoryNotifier.value;
- ExplorerActionDelegate(directory: directory).onActionSelected(context, action);
+ if (directory != null) {
+ ExplorerActionDelegate(directory: directory).onActionSelected(context, action);
+ }
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
@@ -138,10 +142,10 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse
Widget _buildVolumeSelector(BuildContext context) {
if (_volumes.length == 2) {
- return ValueListenableBuilder(
+ return ValueListenableBuilder(
valueListenable: widget.directoryNotifier,
builder: (context, directory, child) {
- final currentVolume = directory.volumePath;
+ final currentVolume = directory?.volumePath;
final otherVolume = _volumes.firstWhere((volume) => volume.path != currentVolume);
final icon = otherVolume.isRemovable ? AIcons.storageCard : AIcons.storageMain;
return IconButton(
@@ -156,7 +160,7 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse
icon: const Icon(AIcons.storageCard),
onPressed: () async {
_volumes.map((v) {
- final selected = widget.directoryNotifier.value.volumePath == v.path;
+ final selected = widget.directoryNotifier.value?.volumePath == v.path;
final icon = v.isRemovable ? AIcons.storageCard : AIcons.storageMain;
return PopupMenuItem(
value: v,
@@ -167,7 +171,7 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse
),
);
}).toList();
- final volumePath = widget.directoryNotifier.value.volumePath;
+ final volumePath = widget.directoryNotifier.value?.volumePath;
final initialVolume = _volumes.firstWhereOrNull((v) => v.path == volumePath);
final volume = await showDialog(
context: context,
diff --git a/lib/widgets/explorer/explorer_action_delegate.dart b/lib/widgets/explorer/explorer_action_delegate.dart
index 82d938d52..fc7932c63 100644
--- a/lib/widgets/explorer/explorer_action_delegate.dart
+++ b/lib/widgets/explorer/explorer_action_delegate.dart
@@ -9,6 +9,7 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
+import 'package:aves/widgets/stats/stats_page.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -29,6 +30,8 @@ class ExplorerActionDelegate with FeedbackMixin {
return isMain && device.canPinShortcut;
case ExplorerAction.setHome:
return isMain && !useTvLayout;
+ case ExplorerAction.stats:
+ return isMain;
}
}
@@ -36,6 +39,7 @@ class ExplorerActionDelegate with FeedbackMixin {
switch (action) {
case ExplorerAction.addShortcut:
case ExplorerAction.setHome:
+ case ExplorerAction.stats:
return true;
}
}
@@ -47,6 +51,8 @@ class ExplorerActionDelegate with FeedbackMixin {
_addShortcut(context);
case ExplorerAction.setHome:
_setHome(context);
+ case ExplorerAction.stats:
+ _goToStats(context);
}
}
@@ -82,4 +88,23 @@ class ExplorerActionDelegate with FeedbackMixin {
settings.setHome(HomePageSetting.explorer, customExplorerPath: directory.dirPath);
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
}
+
+ void _goToStats(BuildContext context) {
+ final path = directory.dirPath;
+ final filter = PathFilter(path);
+ final collection = CollectionLens(
+ source: context.read(),
+ filters: {filter},
+ );
+
+ Navigator.maybeOf(context)?.push(
+ MaterialPageRoute(
+ settings: const RouteSettings(name: StatsPage.routeName),
+ builder: (context) => StatsPage(
+ entries: collection.sortedEntries.toSet(),
+ source: collection.source,
+ ),
+ ),
+ );
+ }
}
diff --git a/lib/widgets/explorer/explorer_page.dart b/lib/widgets/explorer/explorer_page.dart
index a414a8d95..859a70f72 100644
--- a/lib/widgets/explorer/explorer_page.dart
+++ b/lib/widgets/explorer/explorer_page.dart
@@ -41,21 +41,19 @@ class ExplorerPage extends StatefulWidget {
class _ExplorerPageState extends State {
final List _subscriptions = [];
- final ValueNotifier _directory = ValueNotifier(const VolumeRelativeDirectory(volumePath: '', relativeDir: ''));
+ final ValueNotifier _directory = ValueNotifier(null);
+ final ValueNotifier _contentsDirectory = ValueNotifier(null);
final ValueNotifier> _contents = ValueNotifier([]);
Set get _volumes => androidFileUtils.storageVolumes;
- String get _currentDirectoryPath {
- final dir = _directory.value;
- return pContext.join(dir.volumePath, dir.relativeDir);
- }
+ String? _pathOf(VolumeRelativeDirectory? dir) => dir != null ? pContext.join(dir.volumePath, dir.relativeDir) : null;
@override
void initState() {
super.initState();
final path = widget.path;
- if (path != null) {
+ if (path != null && androidFileUtils.getStorageVolume(path) != null) {
_goTo(path);
} else {
final primaryVolume = _volumes.firstWhereOrNull((v) => v.isPrimary);
@@ -82,15 +80,20 @@ class _ExplorerPageState extends State {
@override
Widget build(BuildContext context) {
- return ValueListenableBuilder(
+ return ValueListenableBuilder(
valueListenable: _directory,
builder: (context, directory, child) {
- final atRoot = directory.relativeDir.isEmpty;
+ final atRoot = directory?.relativeDir.isEmpty ?? true;
return AvesPopScope(
handlers: [
APopHandler(
canPop: (context) => atRoot,
- onPopBlocked: (context) => _goTo(pContext.dirname(_currentDirectoryPath)),
+ onPopBlocked: (context) {
+ final path = _pathOf(directory);
+ if (path != null) {
+ _goTo(pContext.dirname(path));
+ }
+ },
),
tvNavigationPopHandler,
doubleBackPopHandler,
@@ -118,7 +121,7 @@ class _ExplorerPageState extends State {
AnimationLimiter(
// animation limiter should not be above the app bar
// so that the crumb line can automatically scroll
- key: ValueKey(_currentDirectoryPath),
+ key: ValueKey(contents),
child: SliverList.builder(
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
@@ -147,18 +150,26 @@ class _ExplorerPageState extends State {
),
),
const Divider(height: 0),
- SafeArea(
- top: false,
- bottom: true,
- child: Padding(
- padding: const EdgeInsets.all(8),
- child: AvesFilterChip(
- filter: PathFilter(_currentDirectoryPath),
- maxWidth: double.infinity,
- onTap: (filter) => _goToCollectionPage(context, filter),
- onLongPress: null,
- ),
- ),
+ ValueListenableBuilder(
+ valueListenable: _contentsDirectory,
+ builder: (context, contentsDirectory, child) {
+ final dirPath = _pathOf(contentsDirectory);
+ return dirPath != null
+ ? SafeArea(
+ top: false,
+ bottom: true,
+ child: Padding(
+ padding: const EdgeInsets.all(8),
+ child: AvesFilterChip(
+ filter: PathFilter(dirPath),
+ maxWidth: double.infinity,
+ onTap: (filter) => _goToCollectionPage(context, filter),
+ onLongPress: null,
+ ),
+ ),
+ )
+ : const SizedBox();
+ },
),
],
),
@@ -177,15 +188,18 @@ class _ExplorerPageState extends State {
if (loading) {
bottom = const CircularProgressIndicator();
} else {
- final source = context.read();
- final album = _getAlbumPath(source, Directory(_currentDirectoryPath));
- if (album != null) {
- bottom = AvesFilterChip(
- filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
- maxWidth: double.infinity,
- onTap: (filter) => _goToCollectionPage(context, filter),
- onLongPress: null,
- );
+ final dirPath = _pathOf(_contentsDirectory.value);
+ if (dirPath != null) {
+ final source = context.read();
+ final album = _getAlbumPath(source, Directory(dirPath));
+ if (album != null) {
+ bottom = AvesFilterChip(
+ filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
+ maxWidth: double.infinity,
+ onTap: (filter) => _goToCollectionPage(context, filter),
+ onLongPress: null,
+ );
+ }
}
}
@@ -241,16 +255,22 @@ class _ExplorerPageState extends State {
}
void _goTo(String path) {
- _directory.value = androidFileUtils.relativeDirectoryFromPath(path)!;
- _updateContents();
+ final dir = androidFileUtils.relativeDirectoryFromPath(path);
+ if (dir != null) {
+ _directory.value = dir;
+ _updateContents();
+ }
}
void _updateContents() {
- final contents = [];
+ final directory = _directory.value;
+ final dirPath = _pathOf(directory);
+ if (dirPath == null) return;
+ final contents = [];
final source = context.read();
final albums = source.rawAlbums.map((v) => v.toLowerCase()).toSet();
- Directory(_currentDirectoryPath).list().listen((event) {
+ Directory(dirPath).list().listen((event) {
final entity = event.absolute;
if (entity is Directory) {
final dirPath = entity.path.toLowerCase();
@@ -265,6 +285,7 @@ class _ExplorerPageState extends State {
final nameB = pContext.split(b.path).last;
return compareAsciiUpperCaseNatural(nameA, nameB);
});
+ _contentsDirectory.value = directory;
});
}
diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart
index fd346316d..f2b1641e6 100644
--- a/lib/widgets/filter_grids/albums_page.dart
+++ b/lib/widgets/filter_grids/albums_page.dart
@@ -1,4 +1,4 @@
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/covers.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/filters/album.dart';
diff --git a/lib/widgets/filter_grids/common/covered_filter_chip.dart b/lib/widgets/filter_grids/common/covered_filter_chip.dart
index 3581bed66..bf6d318aa 100644
--- a/lib/widgets/filter_grids/common/covered_filter_chip.dart
+++ b/lib/widgets/filter_grids/common/covered_filter_chip.dart
@@ -1,6 +1,6 @@
import 'dart:math';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/covers.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
@@ -27,7 +27,7 @@ class CoveredFilterChip extends StatelessWidget {
final double extent, thumbnailExtent;
final bool showText, pinned, locked;
final String? banner;
- final FilterCallback? onTap;
+ final AFilterCallback? onTap;
final HeroType heroType;
const CoveredFilterChip({
diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart
index 5ec822745..f5c176819 100644
--- a/lib/widgets/home_page.dart
+++ b/lib/widgets/home_page.dart
@@ -2,11 +2,12 @@ import 'dart:async';
import 'package:aves/app_mode.dart';
import 'package:aves/model/app/permissions.dart';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/catalog.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
+import 'package:aves/model/app/intent.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
@@ -28,7 +29,6 @@ import 'package:aves/widgets/editor/entry_editor_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/widgets/intent.dart';
import 'package:aves/widgets/search/search_delegate.dart';
import 'package:aves/widgets/settings/home_widget_settings_page.dart';
import 'package:aves/widgets/settings/screen_saver_settings_page.dart';
@@ -107,7 +107,7 @@ class _HomePageState extends State {
unawaited(appInventory.initAppNames());
}
- if (intentData.isNotEmpty) {
+ if (intentData.values.whereNotNull().isNotEmpty) {
await reportService.log('Intent data=$intentData');
switch (intentAction) {
case IntentActions.view:
diff --git a/lib/widgets/home_widget.dart b/lib/widgets/home_widget.dart
index 6f6d4a509..5344627ea 100644
--- a/lib/widgets/home_widget.dart
+++ b/lib/widgets/home_widget.dart
@@ -36,7 +36,7 @@ class HomeWidgetPainter {
final widthPx = sizeDip.width * devicePixelRatio;
final heightPx = sizeDip.height * devicePixelRatio;
final widgetSizePx = Size(widthPx, heightPx);
- debugPrint('draw widget for $sizeDip dp ($widgetSizePx px), entry=$entry');
+ debugPrint('Draw widget for ${sizeDip.width}x${sizeDip.height} dp (${widgetSizePx.width}x${widgetSizePx.height} px), entry=$entry');
final ui.Image? entryImage;
if (entry != null) {
final extent = shape.extentPx(widgetSizePx, entry!) / devicePixelRatio;
diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart
index 528db5eb7..30796f1bf 100644
--- a/lib/widgets/map/map_page.dart
+++ b/lib/widgets/map/map_page.dart
@@ -5,13 +5,14 @@ import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/location.dart';
import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/filters/filters.dart';
-import 'package:aves/model/geotiff.dart';
+import 'package:aves/model/media/geotiff.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/tag.dart';
+import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/view/view.dart';
@@ -28,6 +29,7 @@ import 'package:aves/widgets/common/map/geo_map.dart';
import 'package:aves/widgets/common/map/map_action_delegate.dart';
import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
import 'package:aves/widgets/common/providers/map_theme_provider.dart';
+import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
import 'package:aves/widgets/map/scroller.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
@@ -188,6 +190,8 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_goToCollection(notification.filter);
} else if (notification is FilterNotification) {
_goToCollection(notification.filter);
+ } else if (notification is OpenMapAppNotification) {
+ _openMapApp();
} else {
return false;
}
@@ -434,6 +438,15 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
);
}
+ Future _openMapApp() async {
+ final latLng = _dotEntryNotifier.value?.latLng ?? _mapController.idleBounds?.projectedCenter;
+ if (latLng != null) {
+ await appService.openMap(latLng).then((success) {
+ if (!success) showNoMatchingAppDialog(context);
+ });
+ }
+ }
+
// overlay
void _toggleOverlay() => _overlayVisible.value = !_overlayVisible.value;
diff --git a/lib/widgets/navigation/nav_bar/nav_bar.dart b/lib/widgets/navigation/nav_bar/nav_bar.dart
index e4c3a9f3f..0b7891c72 100644
--- a/lib/widgets/navigation/nav_bar/nav_bar.dart
+++ b/lib/widgets/navigation/nav_bar/nav_bar.dart
@@ -75,42 +75,47 @@ class _AppBottomNavBarState extends State {
const AvesBottomNavItem(route: AlbumListPage.routeName),
];
- Widget child = AvesFloatingBar(
- builder: (context, backgroundColor, child) => BottomNavigationBar(
- items: items
- .map((item) => BottomNavigationBarItem(
- icon: item.icon(context),
- label: item.label(context),
- tooltip: item.label(context),
- ))
- .toList(),
- onTap: (index) => _goTo(context, items, index),
- currentIndex: _getCurrentIndex(context, items),
- type: BottomNavigationBarType.fixed,
- backgroundColor: backgroundColor,
- showSelectedLabels: false,
- showUnselectedLabels: false,
- ),
- );
-
- return Hero(
- tag: 'nav-bar',
- flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {
- return MediaQuery.removeViewInsets(
- context: context,
- removeBottom: true,
- child: toHero.widget,
- );
- },
- child: FloatingNavBar(
- scrollController: PrimaryScrollController.of(context),
- events: widget.events,
- childHeight: AppBottomNavBar.height + context.select((mq) => mq.effectiveBottomPadding),
- child: SafeArea(
- child: child,
+ Widget child = FloatingNavBar(
+ scrollController: PrimaryScrollController.of(context),
+ events: widget.events,
+ childHeight: AppBottomNavBar.height + context.select((mq) => mq.effectiveBottomPadding),
+ child: SafeArea(
+ child: AvesFloatingBar(
+ builder: (context, backgroundColor, child) => BottomNavigationBar(
+ items: items
+ .map((item) => BottomNavigationBarItem(
+ icon: item.icon(context),
+ label: item.label(context),
+ tooltip: item.label(context),
+ ))
+ .toList(),
+ onTap: (index) => _goTo(context, items, index),
+ currentIndex: _getCurrentIndex(context, items),
+ type: BottomNavigationBarType.fixed,
+ backgroundColor: backgroundColor,
+ showSelectedLabels: false,
+ showUnselectedLabels: false,
+ ),
),
),
);
+
+ final animate = context.select((v) => v.animate);
+ if (animate) {
+ child = Hero(
+ tag: 'nav-bar',
+ flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {
+ return MediaQuery.removeViewInsets(
+ context: context,
+ removeBottom: true,
+ child: toHero.widget,
+ );
+ },
+ child: child,
+ );
+ }
+
+ return child;
}
void _onCollectionFilterChanged() => setState(() {});
diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart
index 27248437c..063d17ac7 100644
--- a/lib/widgets/settings/common/quick_actions/editor_page.dart
+++ b/lib/widgets/settings/common/quick_actions/editor_page.dart
@@ -155,7 +155,7 @@ class _QuickActionEditorBodyState extends State widget.save(_quickActions),
+ onPopInvokedWithResult: (didPop, result) => widget.save(_quickActions),
child: ListView(
children: [
Padding(
diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart
index ca7fd703f..718374269 100644
--- a/lib/widgets/settings/navigation/drawer.dart
+++ b/lib/widgets/settings/navigation/drawer.dart
@@ -104,7 +104,7 @@ class _NavigationDrawerEditorPageState extends State
),
body: PopScope(
canPop: true,
- onPopInvoked: (didPop) {
+ onPopInvokedWithResult: (didPop, result) {
settings.drawerTypeBookmarks = _typeItems.where(_visibleTypes.contains).toList();
settings.drawerAlbumBookmarks = _albumItems;
settings.drawerPageBookmarks = _pageItems.where(_visiblePages.contains).toList();
diff --git a/lib/widgets/settings/privacy/file_picker/crumb_line.dart b/lib/widgets/settings/privacy/file_picker/crumb_line.dart
index d334807cc..6ba442830 100644
--- a/lib/widgets/settings/privacy/file_picker/crumb_line.dart
+++ b/lib/widgets/settings/privacy/file_picker/crumb_line.dart
@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CrumbLine extends StatefulWidget {
- final VolumeRelativeDirectory directory;
+ final VolumeRelativeDirectory? directory;
final void Function(String path) onTap;
const CrumbLine({
@@ -25,7 +25,7 @@ class CrumbLine extends StatefulWidget {
class _CrumbLineState extends State {
final ScrollController _scrollController = ScrollController();
- VolumeRelativeDirectory get directory => widget.directory;
+ VolumeRelativeDirectory? get directory => widget.directory;
@override
void dispose() {
@@ -36,7 +36,7 @@ class _CrumbLineState extends State {
@override
void didUpdateWidget(covariant CrumbLine oldWidget) {
super.didUpdateWidget(oldWidget);
- if (oldWidget.directory.relativeDir.length < widget.directory.relativeDir.length) {
+ if ((oldWidget.directory?.relativeDir.length ?? 0) < (widget.directory?.relativeDir.length ?? 0)) {
// scroll to show last crumb
WidgetsBinding.instance.addPostFrameCallback((_) {
final animate = context.read().animate;
@@ -56,10 +56,15 @@ class _CrumbLineState extends State {
@override
Widget build(BuildContext context) {
- List parts = [
- directory.getVolumeDescription(context),
- ...pContext.split(directory.relativeDir),
- ];
+ final _directory = directory;
+ final parts = [];
+ if (_directory != null) {
+ parts.addAll([
+ _directory.getVolumeDescription(context),
+ ...pContext.split(_directory.relativeDir),
+ ]);
+ }
+
final crumbColor = DefaultTextStyle.of(context).style.color;
return ListView.builder(
scrollDirection: Axis.horizontal,
@@ -84,13 +89,15 @@ class _CrumbLineState extends State {
);
}
return GestureDetector(
- onTap: () {
- final path = pContext.joinAll([
- directory.volumePath,
- ...parts.skip(1).take(index),
- ]);
- widget.onTap(path);
- },
+ onTap: _directory != null
+ ? () {
+ final path = pContext.joinAll([
+ _directory.volumePath,
+ ...parts.skip(1).take(index),
+ ]);
+ widget.onTap(path);
+ }
+ : null,
child: Container(
// use a `Container` with a dummy color to make it expand
// so that we can also detect taps around the title `Text`
diff --git a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart
index d8d5c09b2..32c8871cb 100644
--- a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart
+++ b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart
@@ -62,7 +62,7 @@ class _FilePickerPageState extends State {
final animations = context.select((s) => s.accessibilityAnimations);
return PopScope(
canPop: _directory.relativeDir.isEmpty,
- onPopInvoked: (didPop) {
+ onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
final parent = pContext.dirname(currentDirectoryPath);
diff --git a/lib/widgets/stats/date/histogram.dart b/lib/widgets/stats/date/histogram.dart
index 4ad68866a..29b949140 100644
--- a/lib/widgets/stats/date/histogram.dart
+++ b/lib/widgets/stats/date/histogram.dart
@@ -23,7 +23,7 @@ import 'package:provider/provider.dart';
class Histogram extends StatefulWidget {
final Set entries;
final Duration animationDuration;
- final FilterCallback onFilterSelection;
+ final AFilterCallback onFilterSelection;
const Histogram({
super.key,
diff --git a/lib/widgets/stats/filter_table.dart b/lib/widgets/stats/filter_table.dart
index 59b5234c7..b3d36a421 100644
--- a/lib/widgets/stats/filter_table.dart
+++ b/lib/widgets/stats/filter_table.dart
@@ -16,7 +16,7 @@ class FilterTable extends StatelessWidget {
final CollectionFilter Function(T key) filterBuilder;
final bool sortByCount;
final int? maxRowCount;
- final FilterCallback onFilterSelection;
+ final AFilterCallback onFilterSelection;
const FilterTable({
super.key,
diff --git a/lib/widgets/stats/mime_donut.dart b/lib/widgets/stats/mime_donut.dart
index 83a173fda..45fcf5a74 100644
--- a/lib/widgets/stats/mime_donut.dart
+++ b/lib/widgets/stats/mime_donut.dart
@@ -12,7 +12,7 @@ class MimeDonut extends StatelessWidget {
final IconData icon;
final Map byMimeTypes;
final Duration animationDuration;
- final FilterCallback onFilterSelection;
+ final AFilterCallback onFilterSelection;
const MimeDonut({
super.key,
diff --git a/lib/widgets/stats/stats_page.dart b/lib/widgets/stats/stats_page.dart
index 56678c60b..feb69e5dd 100644
--- a/lib/widgets/stats/stats_page.dart
+++ b/lib/widgets/stats/stats_page.dart
@@ -364,7 +364,7 @@ class StatsTopPage extends StatelessWidget {
final String title;
final WidgetBuilder tableBuilder;
- final FilterCallback onFilterSelection;
+ final AFilterCallback onFilterSelection;
const StatsTopPage({
super.key,
diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart
index b63fd1dd2..41795c046 100644
--- a/lib/widgets/viewer/action/entry_info_action_delegate.dart
+++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart
@@ -7,9 +7,8 @@ import 'package:aves/model/entry/extensions/info.dart';
import 'package:aves/model/entry/extensions/metadata_edition.dart';
import 'package:aves/model/entry/extensions/multipage.dart';
import 'package:aves/model/entry/extensions/props.dart';
-import 'package:aves/model/events.dart';
import 'package:aves/model/filters/filters.dart';
-import 'package:aves/model/geotiff.dart';
+import 'package:aves/model/media/geotiff.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/ref/mime_types.dart';
diff --git a/lib/widgets/viewer/controls/notifications.dart b/lib/widgets/viewer/controls/notifications.dart
index 7d5d9006f..ab9c78fa3 100644
--- a/lib/widgets/viewer/controls/notifications.dart
+++ b/lib/widgets/viewer/controls/notifications.dart
@@ -126,3 +126,6 @@ class FullImageLoadedNotification extends Notification {
const FullImageLoadedNotification(this.entry, this.image);
}
+
+@immutable
+class PopupMenuOpenedNotification extends Notification {}
diff --git a/lib/widgets/viewer/debug/db.dart b/lib/widgets/viewer/debug/db.dart
index 7d156a89e..ef99c82ca 100644
--- a/lib/widgets/viewer/debug/db.dart
+++ b/lib/widgets/viewer/debug/db.dart
@@ -3,7 +3,7 @@ import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata/trash.dart';
import 'package:aves/model/source/collection_source.dart';
-import 'package:aves/model/video_playback.dart';
+import 'package:aves/model/viewer/video_playback.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/viewer/info/common.dart';
import 'package:collection/collection.dart';
diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart
index 83292311a..9d7df2ad0 100644
--- a/lib/widgets/viewer/entry_vertical_pager.dart
+++ b/lib/widgets/viewer/entry_vertical_pager.dart
@@ -33,12 +33,12 @@ class ViewerVerticalPageView extends StatefulWidget {
final ValueNotifier entryNotifier;
final ViewerController viewerController;
final Animation overlayOpacity;
- final PageController horizontalPager, verticalPager;
+ final PageController verticalPager, horizontalPager;
final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged;
final VoidCallback onImagePageRequested;
final void Function(AvesEntry mainEntry, AvesEntry? pageEntry) onViewDisposed;
- // critically damped spring a bit stiffer than `ScrollPhysics._kDefaultSpring`
+ // critically damped spring (ratio = 1) a bit stiffer than `ScrollPhysics._kDefaultSpring`
static final spring = SpringDescription.withDampingRatio(
mass: 0.5,
stiffness: 140.0,
@@ -78,6 +78,10 @@ class _ViewerVerticalPageViewState extends State {
AvesEntry? get entry => widget.entryNotifier.value;
+ PageController get verticalPager => widget.verticalPager;
+
+ PageController get horizontalPager => widget.horizontalPager;
+
static const double maximumBrightness = 1.0;
@override
@@ -146,10 +150,10 @@ class _ViewerVerticalPageViewState extends State {
return true;
},
child: AnimatedBuilder(
- animation: widget.verticalPager,
+ animation: verticalPager,
builder: (context, child) {
return Visibility(
- visible: widget.verticalPager.page! > 1,
+ visible: verticalPager.page! > 1,
child: child!,
);
},
@@ -185,7 +189,7 @@ class _ViewerVerticalPageViewState extends State {
// key is expected by test driver
key: const Key('vertical-pageview'),
scrollDirection: Axis.vertical,
- controller: widget.verticalPager,
+ controller: verticalPager,
physics: MagnifierScrollerPhysics(
gestureSettings: MediaQuery.gestureSettingsOf(context),
parent: SpringyScrollPhysics(
@@ -217,7 +221,7 @@ class _ViewerVerticalPageViewState extends State {
child = MultiEntryScroller(
collection: collection!,
viewerController: widget.viewerController,
- pageController: widget.horizontalPager,
+ pageController: horizontalPager,
onPageChanged: widget.onHorizontalPageChanged,
onViewDisposed: widget.onViewDisposed,
);
@@ -299,8 +303,7 @@ class _ViewerVerticalPageViewState extends State {
}
void _goToHorizontalPage(int delta, {required bool animate}) {
- final pageController = widget.horizontalPager;
- final page = pageController.page?.round();
+ final page = horizontalPager.page?.round();
final _collection = collection;
if (page != null && _collection != null) {
var target = page + delta;
@@ -308,19 +311,19 @@ class _ViewerVerticalPageViewState extends State {
target = target.clamp(0, _collection.entryCount - 1);
}
if (animate) {
- pageController.animateToPage(
+ horizontalPager.animateToPage(
target,
duration: ADurations.viewerHorizontalPageAnimation,
curve: Curves.easeInOutCubic,
);
} else {
- pageController.jumpToPage(target);
+ horizontalPager.jumpToPage(target);
}
}
}
void _onVerticalPageControllerChanged() {
- final page = widget.verticalPager.page!;
+ final page = verticalPager.page!;
final opacity = min(1.0, page);
_backgroundOpacityNotifier.value = opacity * opacity;
diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart
index 5eb4dfbe0..850d2e9de 100644
--- a/lib/widgets/viewer/entry_viewer_stack.dart
+++ b/lib/widgets/viewer/entry_viewer_stack.dart
@@ -219,7 +219,7 @@ class _EntryViewerStackState extends State with EntryViewContr
final viewStateConductor = context.read();
return PopScope(
canPop: false,
- onPopInvoked: (didPop) {
+ onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
_onPopInvoked();
@@ -494,13 +494,6 @@ class _EntryViewerStackState extends State with EntryViewContr
);
}
},
- onActionMenuOpened: () {
- // if the menu is opened while overlay is hiding,
- // the popup menu button is disposed and menu items are ineffective,
- // so we make sure overlay stays visible
- _videoActionDelegate.stopOverlayHidingTimer();
- const ToggleOverlayNotification(visible: true).dispatch(context);
- },
),
);
} else if (targetEntry.is360) {
@@ -602,6 +595,13 @@ class _EntryViewerStackState extends State with EntryViewContr
case MoveType.export:
break;
}
+ } else if (notification is PopupMenuOpenedNotification) {
+ // if the menu is opened while overlay is hiding,
+ // the popup menu button is disposed and menu items are ineffective,
+ // so we make sure overlay stays visible
+ _overlayVisible.value = true;
+ _videoActionDelegate.stopOverlayHidingTimer();
+ dismissFeedback(context);
} else if (notification is ToggleOverlayNotification) {
_overlayVisible.value = notification.visible ?? !_overlayVisible.value;
} else if (notification is LockViewNotification) {
@@ -712,16 +712,23 @@ class _EntryViewerStackState extends State with EntryViewContr
void _onVerticalPageChanged(int page) {
_currentVerticalPage.value = page;
_overrideSnackBarMargin();
+ final animate = context.read().animate;
switch (page) {
case transitionPage:
dismissFeedback(context);
_popVisual();
+ if (!animate) {
+ _verticalPager.jumpToPage(page);
+ }
case imagePage:
reportService.log('Nav move to Image page');
case infoPage:
reportService.log('Nav move to Info page');
// prevent hero when viewer is offscreen
_heroInfoNotifier.value = null;
+ if (!animate) {
+ _verticalPager.jumpToPage(page);
+ }
}
}
diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart
index 42080956b..13390eab7 100644
--- a/lib/widgets/viewer/info/basic_section.dart
+++ b/lib/widgets/viewer/info/basic_section.dart
@@ -1,6 +1,6 @@
import 'package:aves/app_mode.dart';
import 'package:aves/image_providers/app_icon_image_provider.dart';
-import 'package:aves/model/apps.dart';
+import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/favourites.dart';
import 'package:aves/model/entry/extensions/multipage.dart';
@@ -41,7 +41,7 @@ class BasicSection extends StatefulWidget {
final EntryInfoActionDelegate actionDelegate;
final ValueNotifier isScrollingNotifier;
final ValueNotifier isEditingMetadataNotifier;
- final FilterCallback onFilter;
+ final AFilterCallback onFilter;
const BasicSection({
super.key,
diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart
index ca342a284..4cd8781df 100644
--- a/lib/widgets/viewer/info/info_page.dart
+++ b/lib/widgets/viewer/info/info_page.dart
@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/multipage.dart';
-import 'package:aves/model/events.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/settings.dart';
@@ -15,10 +14,10 @@ import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/info/basic_section.dart';
+import 'package:aves/widgets/viewer/info/color_section.dart';
import 'package:aves/widgets/viewer/info/embedded/embedded_data_opener.dart';
import 'package:aves/widgets/viewer/info/info_app_bar.dart';
import 'package:aves/widgets/viewer/info/location_section.dart';
-import 'package:aves/widgets/viewer/info/color_section.dart';
import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart';
import 'package:aves/widgets/viewer/info/metadata/metadata_section.dart';
import 'package:aves/widgets/viewer/multipage/conductor.dart';
diff --git a/lib/widgets/viewer/info/location_section.dart b/lib/widgets/viewer/info/location_section.dart
index 84606cb3b..c3a75c341 100644
--- a/lib/widgets/viewer/info/location_section.dart
+++ b/lib/widgets/viewer/info/location_section.dart
@@ -10,7 +10,9 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/common/map/geo_map.dart';
+import 'package:aves/widgets/common/map/map_action_delegate.dart';
import 'package:aves/widgets/common/providers/map_theme_provider.dart';
+import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/map/map_page.dart';
import 'package:aves/widgets/viewer/info/common.dart';
import 'package:aves_map/aves_map.dart';
@@ -22,7 +24,7 @@ class LocationSection extends StatefulWidget {
final AvesEntry entry;
final bool showTitle;
final ValueNotifier isScrollingNotifier;
- final FilterCallback onFilter;
+ final AFilterCallback onFilter;
const LocationSection({
super.key,
@@ -76,63 +78,72 @@ class _LocationSectionState extends State {
if (!entry.hasGps) return const SizedBox();
final canNavigate = context.select, bool>((v) => v.value.canNavigate);
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (widget.showTitle) const SectionRow(icon: AIcons.location),
- MapTheme(
- interactive: false,
- showCoordinateFilter: false,
- navigationButton: canNavigate ? MapNavigationButton.map : MapNavigationButton.none,
- visualDensity: VisualDensity.compact,
- mapHeight: 200,
- child: GeoMap(
- controller: _mapController,
- entries: [entry],
- availableSize: MediaQuery.sizeOf(context),
- isAnimatingNotifier: widget.isScrollingNotifier,
- onUserZoomChange: (zoom) => settings.infoMapZoom = zoom.roundToDouble(),
- onMarkerTap: collection != null && canNavigate ? (location, entry) => _openMapPage(context) : null,
- openMapPage: collection != null ? _openMapPage : null,
+ return NotificationListener(
+ onNotification: (notification) {
+ if (notification is OpenMapAppNotification) {
+ _openMapApp();
+ return true;
+ }
+ return false;
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (widget.showTitle) const SectionRow(icon: AIcons.location),
+ MapTheme(
+ interactive: false,
+ showCoordinateFilter: false,
+ navigationButton: canNavigate ? MapNavigationButton.map : MapNavigationButton.none,
+ visualDensity: VisualDensity.compact,
+ mapHeight: 200,
+ child: GeoMap(
+ controller: _mapController,
+ entries: [entry],
+ availableSize: MediaQuery.sizeOf(context),
+ isAnimatingNotifier: widget.isScrollingNotifier,
+ onUserZoomChange: (zoom) => settings.infoMapZoom = zoom.roundToDouble(),
+ onMarkerTap: collection != null && canNavigate ? (location, entry) => _openMapPage(context) : null,
+ openMapPage: collection != null ? _openMapPage : null,
+ ),
),
- ),
- AnimatedBuilder(
- animation: entry.addressChangeNotifier,
- builder: (context, child) {
- final filters = [];
- if (entry.hasAddress) {
- final address = entry.addressDetails!;
- final country = address.countryName;
- if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country${LocationFilter.locationSeparator}${address.countryCode}'));
- final state = address.stateName;
- if (state != null && state.isNotEmpty) filters.add(LocationFilter(LocationLevel.state, '$state${LocationFilter.locationSeparator}${address.stateCode}'));
- final place = address.place;
- if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place));
- }
+ AnimatedBuilder(
+ animation: entry.addressChangeNotifier,
+ builder: (context, child) {
+ final filters = [];
+ if (entry.hasAddress) {
+ final address = entry.addressDetails!;
+ final country = address.countryName;
+ if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country${LocationFilter.locationSeparator}${address.countryCode}'));
+ final state = address.stateName;
+ if (state != null && state.isNotEmpty) filters.add(LocationFilter(LocationLevel.state, '$state${LocationFilter.locationSeparator}${address.stateCode}'));
+ final place = address.place;
+ if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place));
+ }
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- _AddressInfoGroup(entry: entry),
- if (filters.isNotEmpty)
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8),
- child: Wrap(
- spacing: 8,
- runSpacing: 8,
- children: filters
- .map((filter) => AvesFilterChip(
- filter: filter,
- onTap: widget.onFilter,
- ))
- .toList(),
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _AddressInfoGroup(entry: entry),
+ if (filters.isNotEmpty)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8),
+ child: Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: filters
+ .map((filter) => AvesFilterChip(
+ filter: filter,
+ onTap: widget.onFilter,
+ ))
+ .toList(),
+ ),
),
- ),
- ],
- );
- },
- ),
- ],
+ ],
+ );
+ },
+ ),
+ ],
+ ),
);
}
@@ -155,6 +166,15 @@ class _LocationSectionState extends State {
);
}
+ Future _openMapApp() async {
+ final latLng = entry.latLng;
+ if (latLng != null) {
+ await appService.openMap(latLng).then((success) {
+ if (!success) showNoMatchingAppDialog(context);
+ });
+ }
+ }
+
void _onMetadataChanged() {
setState(() {});
diff --git a/lib/widgets/viewer/overlay/details/expander.dart b/lib/widgets/viewer/overlay/details/expander.dart
index 6dd3d45d1..f7fd84df7 100644
--- a/lib/widgets/viewer/overlay/details/expander.dart
+++ b/lib/widgets/viewer/overlay/details/expander.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
-class OverlayRowExpander extends StatelessWidget {
+class OverlayRowExpander extends StatefulWidget {
final ValueNotifier expandedNotifier;
final Widget child;
@@ -10,24 +11,69 @@ class OverlayRowExpander extends StatelessWidget {
required this.child,
});
+ @override
+ State createState() => _OverlayRowExpanderState();
+}
+
+class _OverlayRowExpanderState extends State {
+ final ScrollController _scrollController = ScrollController();
+
+ @override
+ void didUpdateWidget(covariant OverlayRowExpander oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ if (oldWidget.child != widget.child) {
+ if (_scrollController.hasClients && _scrollController.positions.every((v) => v.hasContentDimensions)) {
+ _scrollController.jumpTo(0);
+ }
+ }
+ }
+
+ @override
+ void dispose() {
+ _scrollController.dispose();
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
- valueListenable: expandedNotifier,
+ valueListenable: widget.expandedNotifier,
builder: (context, expanded, child) {
final parent = DefaultTextStyle.of(context);
- return DefaultTextStyle(
- key: key,
+ child = DefaultTextStyle(
style: parent.style,
textAlign: parent.textAlign,
softWrap: expanded,
overflow: parent.overflow,
- maxLines: expanded ? 16 : 1,
+ maxLines: expanded ? null : 1,
textWidthBasis: parent.textWidthBasis,
child: child!,
);
+ if (expanded) {
+ child = ConstrainedBox(
+ constraints: BoxConstraints(
+ maxHeight: context.select((mq) => mq.size.height / 5),
+ ),
+ child: MediaQuery.removePadding(
+ // remove padding so that scroll bar is consistent with the scroll view
+ context: context,
+ removeTop: true,
+ removeBottom: true,
+ child: Scrollbar(
+ controller: _scrollController,
+ thumbVisibility: true,
+ radius: const Radius.circular(16),
+ child: SingleChildScrollView(
+ controller: _scrollController,
+ child: child,
+ ),
+ ),
+ ),
+ );
+ }
+ return child;
},
- child: child,
+ child: widget.child,
);
}
}
diff --git a/lib/widgets/viewer/overlay/minimap.dart b/lib/widgets/viewer/overlay/minimap.dart
index 5c63c5b20..cba86e77d 100644
--- a/lib/widgets/viewer/overlay/minimap.dart
+++ b/lib/widgets/viewer/overlay/minimap.dart
@@ -1,7 +1,7 @@
import 'dart:math';
import 'dart:ui';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/editor/transform/controller.dart';
import 'package:aves/widgets/editor/transform/transformation.dart';
diff --git a/lib/widgets/viewer/overlay/selection_button.dart b/lib/widgets/viewer/overlay/selection_button.dart
index 9549dbf82..d7f9f94e2 100644
--- a/lib/widgets/viewer/overlay/selection_button.dart
+++ b/lib/widgets/viewer/overlay/selection_button.dart
@@ -15,7 +15,6 @@ class SelectionButton extends StatelessWidget {
final Animation scale;
static const double padding = 8;
- static const duration = ADurations.thumbnailOverlayAnimation;
const SelectionButton({
super.key,
@@ -27,6 +26,7 @@ class SelectionButton extends StatelessWidget {
Widget build(BuildContext context) {
final l10n = context.l10n;
final selection = context.read>();
+ final duration = context.select((v) => v.formTransition);
return SafeArea(
top: false,
bottom: false,
diff --git a/lib/widgets/viewer/overlay/video/video.dart b/lib/widgets/viewer/overlay/video/video.dart
index 2d9b97d6b..eb38e103a 100644
--- a/lib/widgets/viewer/overlay/video/video.dart
+++ b/lib/widgets/viewer/overlay/video/video.dart
@@ -16,7 +16,6 @@ class VideoControlOverlay extends StatefulWidget {
final AvesVideoController? controller;
final Animation scale;
final Function(EntryAction value) onActionSelected;
- final VoidCallback onActionMenuOpened;
const VideoControlOverlay({
super.key,
@@ -24,7 +23,6 @@ class VideoControlOverlay extends StatefulWidget {
required this.controller,
required this.scale,
required this.onActionSelected,
- required this.onActionMenuOpened,
});
@override
diff --git a/lib/widgets/viewer/overlay/viewer_buttons.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart
index bcf6287d3..2b757b544 100644
--- a/lib/widgets/viewer/overlay/viewer_buttons.dart
+++ b/lib/widgets/viewer/overlay/viewer_buttons.dart
@@ -19,7 +19,6 @@ import 'package:aves/widgets/common/action_controls/togglers/play.dart';
import 'package:aves/widgets/common/basic/font_size_icon_theme.dart';
import 'package:aves/widgets/common/basic/popup/container.dart';
import 'package:aves/widgets/common/basic/popup/expansion_panel.dart';
-import 'package:aves/widgets/common/basic/popup/menu_button.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
@@ -269,7 +268,7 @@ class _ViewerButtonRowContentState extends State {
child: OverlayButton(
scale: widget.scale,
child: FontSizeIconTheme(
- child: AvesPopupMenuButton(
+ child: PopupMenuButton(
key: const Key('entry-menu-button'),
itemBuilder: (context) {
final exportInternalActions = exportActions.whereNot(EntryActions.exportExternal.contains).toList();
@@ -305,6 +304,7 @@ class _ViewerButtonRowContentState extends State {
]
];
},
+ onOpened: () => PopupMenuOpenedNotification().dispatch(context),
onSelected: (action) async {
_popupExpandedNotifier.value = null;
// wait for the popup menu to hide before proceeding with the action
@@ -315,12 +315,6 @@ class _ViewerButtonRowContentState extends State {
_popupExpandedNotifier.value = null;
},
iconSize: IconTheme.of(context).size,
- onMenuOpened: () {
- // if the menu is opened while overlay is hiding,
- // the popup menu button is disposed and menu items are ineffective,
- // so we make sure overlay stays visible
- const ToggleOverlayNotification(visible: true).dispatch(context);
- },
popUpAnimationStyle: animations.popUpAnimationStyle,
),
),
diff --git a/lib/widgets/viewer/panorama_page.dart b/lib/widgets/viewer/panorama_page.dart
index aa9c74691..2a1da0ae4 100644
--- a/lib/widgets/viewer/panorama_page.dart
+++ b/lib/widgets/viewer/panorama_page.dart
@@ -2,7 +2,7 @@ import 'dart:math';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/images.dart';
-import 'package:aves/model/panorama.dart';
+import 'package:aves/model/media/panorama.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/aves_app.dart';
@@ -58,7 +58,7 @@ class _PanoramaPageState extends State {
Widget build(BuildContext context) {
return PopScope(
canPop: true,
- onPopInvoked: (didPop) => _onLeave(),
+ onPopInvokedWithResult: (didPop, result) => _onLeave(),
child: AvesScaffold(
body: Stack(
children: [
diff --git a/lib/widgets/viewer/video/db_playback_state_handler.dart b/lib/widgets/viewer/video/db_playback_state_handler.dart
index d27db3957..c862ccc0b 100644
--- a/lib/widgets/viewer/video/db_playback_state_handler.dart
+++ b/lib/widgets/viewer/video/db_playback_state_handler.dart
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:aves/model/settings/settings.dart';
-import 'package:aves/model/video_playback.dart';
+import 'package:aves/model/viewer/video_playback.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
diff --git a/lib/widgets/viewer/view/conductor.dart b/lib/widgets/viewer/view/conductor.dart
index cae0f16cd..78a7def8b 100644
--- a/lib/widgets/viewer/view/conductor.dart
+++ b/lib/widgets/viewer/view/conductor.dart
@@ -1,5 +1,5 @@
import 'package:aves/model/entry/entry.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/viewer/view/controller.dart';
import 'package:aves_magnifier/aves_magnifier.dart';
import 'package:collection/collection.dart';
diff --git a/lib/widgets/viewer/view/controller.dart b/lib/widgets/viewer/view/controller.dart
index 1a5ea02bd..725e99ec1 100644
--- a/lib/widgets/viewer/view/controller.dart
+++ b/lib/widgets/viewer/view/controller.dart
@@ -1,5 +1,5 @@
import 'package:aves/model/entry/entry.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/viewer/view/histogram.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart
index dab017301..2421221f2 100644
--- a/lib/widgets/viewer/visual/entry_page_view.dart
+++ b/lib/widgets/viewer/visual/entry_page_view.dart
@@ -4,7 +4,7 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/settings/settings.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/media/media_session_service.dart';
import 'package:aves/theme/icons.dart';
diff --git a/lib/widgets/viewer/visual/raster.dart b/lib/widgets/viewer/visual/raster.dart
index b4c015b6c..3ca4148eb 100644
--- a/lib/widgets/viewer/visual/raster.dart
+++ b/lib/widgets/viewer/visual/raster.dart
@@ -6,7 +6,7 @@ import 'package:aves/model/entry/extensions/images.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/settings.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/visual/entry_page_view.dart';
diff --git a/lib/widgets/viewer/visual/vector.dart b/lib/widgets/viewer/visual/vector.dart
index 95ced38ed..0a5479848 100644
--- a/lib/widgets/viewer/visual/vector.dart
+++ b/lib/widgets/viewer/visual/vector.dart
@@ -5,7 +5,7 @@ import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/images.dart';
import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/settings.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/utils/math_utils.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
import 'package:aves/widgets/viewer/visual/entry_page_view.dart';
diff --git a/lib/widgets/viewer/visual/video/subtitle/subtitle.dart b/lib/widgets/viewer/visual/video/subtitle/subtitle.dart
index 1855fe33c..06a81b904 100644
--- a/lib/widgets/viewer/visual/video/subtitle/subtitle.dart
+++ b/lib/widgets/viewer/visual/video/subtitle/subtitle.dart
@@ -2,7 +2,7 @@ import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/settings/enums/subtitle_position.dart';
import 'package:aves/model/settings/settings.dart';
-import 'package:aves/model/view_state.dart';
+import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/common/basic/text/background_painter.dart';
import 'package:aves/widgets/common/basic/text/outlined.dart';
import 'package:aves/widgets/viewer/visual/video/subtitle/ass_parser.dart';
diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart
index 10763ee5a..7184edff7 100644
--- a/lib/widgets/wallpaper_page.dart
+++ b/lib/widgets/wallpaper_page.dart
@@ -187,13 +187,6 @@ class _EntryEditorState extends State with EntryViewControllerMixin
controller: videoController,
action: action,
),
- onActionMenuOpened: () {
- // if the menu is opened while overlay is hiding,
- // the popup menu button is disposed and menu items are ineffective,
- // so we make sure overlay stays visible
- _videoActionDelegate.stopOverlayHidingTimer();
- const ToggleOverlayNotification(visible: true).dispatch(context);
- },
),
);
}
diff --git a/plugins/aves_magnifier/lib/src/core/core.dart b/plugins/aves_magnifier/lib/src/core/core.dart
index 0585c5331..395a6234d 100644
--- a/plugins/aves_magnifier/lib/src/core/core.dart
+++ b/plugins/aves_magnifier/lib/src/core/core.dart
@@ -427,7 +427,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM
}
void onAnimationStatus(AnimationStatus status) {
- if (status == AnimationStatus.completed) {
+ if (status.isCompleted) {
onAnimationStatusCompleted();
}
}
diff --git a/plugins/aves_magnifier/pubspec.lock b/plugins/aves_magnifier/pubspec.lock
index d455dc440..49a802af7 100644
--- a/plugins/aves_magnifier/pubspec.lock
+++ b/plugins/aves_magnifier/pubspec.lock
@@ -57,18 +57,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
nested:
dependency: transitive
description:
@@ -99,5 +99,5 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
flutter: ">=1.16.0"
diff --git a/plugins/aves_magnifier/pubspec.yaml b/plugins/aves_magnifier/pubspec.yaml
index b978ceb95..485a526d2 100644
--- a/plugins/aves_magnifier/pubspec.yaml
+++ b/plugins/aves_magnifier/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_map/lib/src/controller.dart b/plugins/aves_map/lib/src/controller.dart
index 59f7af250..26d2b56c4 100644
--- a/plugins/aves_map/lib/src/controller.dart
+++ b/plugins/aves_map/lib/src/controller.dart
@@ -6,6 +6,9 @@ import 'package:latlong2/latlong.dart';
class AvesMapController {
final StreamController _streamController = StreamController.broadcast();
+ ZoomedBounds? _idleBounds;
+
+ ZoomedBounds? get idleBounds => _idleBounds;
Stream get _events => _streamController.stream;
@@ -38,7 +41,10 @@ class AvesMapController {
void zoomBy(double delta) => _streamController.add(MapControllerZoomEvent(delta));
- void notifyIdle(ZoomedBounds bounds) => _streamController.add(MapIdleUpdate(bounds));
+ void notifyIdle(ZoomedBounds bounds) {
+ _idleBounds = bounds;
+ _streamController.add(MapIdleUpdate(bounds));
+ }
void notifyMarkerLocationChange() => _streamController.add(MapMarkerLocationChangeEvent());
}
diff --git a/plugins/aves_map/pubspec.lock b/plugins/aves_map/pubspec.lock
index 86331c820..0e7ea4667 100644
--- a/plugins/aves_map/pubspec.lock
+++ b/plugins/aves_map/pubspec.lock
@@ -153,18 +153,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
mgrs_dart:
dependency: transitive
description:
@@ -283,5 +283,5 @@ packages:
source: hosted
version: "2.0.0"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
flutter: ">=3.10.0"
diff --git a/plugins/aves_map/pubspec.yaml b/plugins/aves_map/pubspec.yaml
index 02f8bf70c..c59350c35 100644
--- a/plugins/aves_map/pubspec.yaml
+++ b/plugins/aves_map/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_model/lib/aves_model.dart b/plugins/aves_model/lib/aves_model.dart
index e406bb5c4..94e32722e 100644
--- a/plugins/aves_model/lib/aves_model.dart
+++ b/plugins/aves_model/lib/aves_model.dart
@@ -1,10 +1,10 @@
library aves_model;
export 'src/actions/chip.dart';
-export 'src/actions/explorer.dart';
export 'src/actions/chip_set.dart';
export 'src/actions/entry.dart';
export 'src/actions/entry_set.dart';
+export 'src/actions/explorer.dart';
export 'src/actions/map.dart';
export 'src/actions/map_cluster.dart';
export 'src/actions/move_type.dart';
@@ -13,6 +13,7 @@ export 'src/actions/share.dart';
export 'src/actions/slideshow.dart';
export 'src/editor/enums.dart';
export 'src/entry/base.dart';
+export 'src/events/actions.dart';
export 'src/metadata/enums.dart';
export 'src/metadata/fields.dart';
export 'src/settings/access.dart';
diff --git a/plugins/aves_model/lib/src/actions/explorer.dart b/plugins/aves_model/lib/src/actions/explorer.dart
index f71619c75..5d33f4fa7 100644
--- a/plugins/aves_model/lib/src/actions/explorer.dart
+++ b/plugins/aves_model/lib/src/actions/explorer.dart
@@ -1,4 +1,5 @@
enum ExplorerAction {
addShortcut,
setHome,
+ stats,
}
diff --git a/plugins/aves_model/lib/src/actions/map.dart b/plugins/aves_model/lib/src/actions/map.dart
index f9be8b8d0..6d4838cf3 100644
--- a/plugins/aves_model/lib/src/actions/map.dart
+++ b/plugins/aves_model/lib/src/actions/map.dart
@@ -1,5 +1,6 @@
enum MapAction {
selectStyle,
+ openMapApp,
zoomIn,
zoomOut,
}
diff --git a/lib/model/events.dart b/plugins/aves_model/lib/src/events/actions.dart
similarity index 100%
rename from lib/model/events.dart
rename to plugins/aves_model/lib/src/events/actions.dart
diff --git a/plugins/aves_model/lib/src/source/enums.dart b/plugins/aves_model/lib/src/source/enums.dart
index de1b1faef..9f9d65e4d 100644
--- a/plugins/aves_model/lib/src/source/enums.dart
+++ b/plugins/aves_model/lib/src/source/enums.dart
@@ -4,7 +4,7 @@ enum ChipSortFactor { date, name, count, size }
enum AlbumChipGroupFactor { none, importance, mimeType, volume }
-enum EntrySortFactor { date, name, rating, size }
+enum EntrySortFactor { date, name, rating, size, duration }
enum EntryGroupFactor { none, album, month, day }
diff --git a/plugins/aves_model/pubspec.lock b/plugins/aves_model/pubspec.lock
index 11efc89f0..aa79241b7 100644
--- a/plugins/aves_model/pubspec.lock
+++ b/plugins/aves_model/pubspec.lock
@@ -50,18 +50,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: "direct main"
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
sky_engine:
dependency: transitive
description: flutter
@@ -76,4 +76,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_model/pubspec.yaml b/plugins/aves_model/pubspec.yaml
index f124ccc3e..21978285a 100644
--- a/plugins/aves_model/pubspec.yaml
+++ b/plugins/aves_model/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_platform_meta/pubspec.lock b/plugins/aves_platform_meta/pubspec.lock
index 7c5da4bb0..f6c43ab3f 100644
--- a/plugins/aves_platform_meta/pubspec.lock
+++ b/plugins/aves_platform_meta/pubspec.lock
@@ -42,18 +42,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
plugin_platform_interface:
dependency: "direct main"
description:
@@ -76,4 +76,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_platform_meta/pubspec.yaml b/plugins/aves_platform_meta/pubspec.yaml
index af8143563..d08c9d0dc 100644
--- a/plugins/aves_platform_meta/pubspec.yaml
+++ b/plugins/aves_platform_meta/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_report/pubspec.lock b/plugins/aves_report/pubspec.lock
index 7b3ee2edf..db84b1151 100644
--- a/plugins/aves_report/pubspec.lock
+++ b/plugins/aves_report/pubspec.lock
@@ -42,18 +42,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
path:
dependency: transitive
description:
@@ -84,4 +84,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_report/pubspec.yaml b/plugins/aves_report/pubspec.yaml
index ef7a356d8..ea36df9af 100644
--- a/plugins/aves_report/pubspec.yaml
+++ b/plugins/aves_report/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_report_console/pubspec.lock b/plugins/aves_report_console/pubspec.lock
index 2e2d20d83..098029261 100644
--- a/plugins/aves_report_console/pubspec.lock
+++ b/plugins/aves_report_console/pubspec.lock
@@ -49,18 +49,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
path:
dependency: transitive
description:
@@ -91,4 +91,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_report_console/pubspec.yaml b/plugins/aves_report_console/pubspec.yaml
index ea25fa1e6..cffe09aa2 100644
--- a/plugins/aves_report_console/pubspec.yaml
+++ b/plugins/aves_report_console/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_report_crashlytics/pubspec.lock b/plugins/aves_report_crashlytics/pubspec.lock
index 6ea2d994f..844067827 100644
--- a/plugins/aves_report_crashlytics/pubspec.lock
+++ b/plugins/aves_report_crashlytics/pubspec.lock
@@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
- sha256: b46f62516902afb04befa4b30eb6a12ac1f58ca8cb25fb9d632407259555dd3d
+ sha256: b1595874fbc8f7a50da90f5d8f327bb0bfd6a95dc906c390efe991540c3b54aa
url: "https://pub.dev"
source: hosted
- version: "1.3.39"
+ version: "1.3.40"
async:
dependency: transitive
description:
@@ -68,42 +68,42 @@ packages:
dependency: "direct main"
description:
name: firebase_core
- sha256: "5159984ce9b70727473eb388394650677c02c925aaa6c9439905e1f30966a4d5"
+ sha256: "3187f4f8e49968573fd7403011dca67ba95aae419bc0d8131500fae160d94f92"
url: "https://pub.dev"
source: hosted
- version: "3.2.0"
+ version: "3.3.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
- sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb"
+ sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02"
url: "https://pub.dev"
source: hosted
- version: "5.1.0"
+ version: "5.2.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
- sha256: "23509cb3cddfb3c910c143279ac3f07f06d3120f7d835e4a5d4b42558e978712"
+ sha256: e8d1e22de72cb21cdcfc5eed7acddab3e99cd83f3b317f54f7a96c32f25fd11e
url: "https://pub.dev"
source: hosted
- version: "2.17.3"
+ version: "2.17.4"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
- sha256: da32da3b441d1bee73ca990085a3ae174b9fb3585229f02a278a2ea42454d784
+ sha256: "30260e1b8ad1464b41ca4531b44ce63d752daaf2f12c92ca6cdcd82b270abecc"
url: "https://pub.dev"
source: hosted
- version: "4.0.3"
+ version: "4.0.4"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
- sha256: b7567106ed57bbadaa0610774cc17a10b82ed04a1aba99790f303385ac4ba78f
+ sha256: a75e1826d92ea4e86e4a753c7b5d64b844a362676fa653185f1581c859186d18
url: "https://pub.dev"
source: hosted
- version: "3.6.39"
+ version: "3.6.40"
flutter:
dependency: "direct main"
description: flutter
@@ -131,18 +131,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
- sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
+ sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
- version: "10.0.4"
+ version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
- sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
+ sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
- version: "3.0.3"
+ version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
@@ -171,18 +171,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
path:
dependency: transitive
description:
@@ -248,10 +248,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
+ sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
- version: "0.7.0"
+ version: "0.7.2"
vector_math:
dependency: transitive
description:
@@ -264,10 +264,10 @@ packages:
dependency: transitive
description:
name: vm_service
- sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
+ sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
- version: "14.2.1"
+ version: "14.2.4"
web:
dependency: transitive
description:
@@ -277,5 +277,5 @@ packages:
source: hosted
version: "0.5.1"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
diff --git a/plugins/aves_report_crashlytics/pubspec.yaml b/plugins/aves_report_crashlytics/pubspec.yaml
index c83afb1c8..c94c91d59 100644
--- a/plugins/aves_report_crashlytics/pubspec.yaml
+++ b/plugins/aves_report_crashlytics/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_screen_state/pubspec.lock b/plugins/aves_screen_state/pubspec.lock
index 7c5da4bb0..f6c43ab3f 100644
--- a/plugins/aves_screen_state/pubspec.lock
+++ b/plugins/aves_screen_state/pubspec.lock
@@ -42,18 +42,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
plugin_platform_interface:
dependency: "direct main"
description:
@@ -76,4 +76,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_screen_state/pubspec.yaml b/plugins/aves_screen_state/pubspec.yaml
index 38ce2fb3e..b4e2f20a2 100644
--- a/plugins/aves_screen_state/pubspec.yaml
+++ b/plugins/aves_screen_state/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_services/pubspec.lock b/plugins/aves_services/pubspec.lock
index 4439e0b74..498f23607 100644
--- a/plugins/aves_services/pubspec.lock
+++ b/plugins/aves_services/pubspec.lock
@@ -160,18 +160,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
mgrs_dart:
dependency: transitive
description:
@@ -290,5 +290,5 @@ packages:
source: hosted
version: "2.0.0"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
flutter: ">=3.10.0"
diff --git a/plugins/aves_services/pubspec.yaml b/plugins/aves_services/pubspec.yaml
index 84791a1b3..575f415a2 100644
--- a/plugins/aves_services/pubspec.yaml
+++ b/plugins/aves_services/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_services_google/pubspec.lock b/plugins/aves_services_google/pubspec.lock
index 3c9b380fb..07d5333b6 100644
--- a/plugins/aves_services_google/pubspec.lock
+++ b/plugins/aves_services_google/pubspec.lock
@@ -89,18 +89,18 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
- sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
+ sha256: "93429694c9253d2871b3af80cf11b3cbb5c65660d402ed7bf69854ce4a089f82"
url: "https://pub.dev"
source: hosted
- version: "10.1.0"
+ version: "10.1.1"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
- sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
+ sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba"
url: "https://pub.dev"
source: hosted
- version: "7.0.0"
+ version: "7.0.1"
equatable:
dependency: transitive
description:
@@ -158,10 +158,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
- sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e
+ sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
url: "https://pub.dev"
source: hosted
- version: "2.0.20"
+ version: "2.0.21"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -195,50 +195,50 @@ packages:
dependency: transitive
description:
name: google_maps
- sha256: "47eef3836b49bb030d5cb3afc60b8451408bf34cf753e571b645d6529eb4251a"
+ sha256: "463b38e5a92a05cde41220a11fd5eef3847031fef3e8cf295ac76ec453246907"
url: "https://pub.dev"
source: hosted
- version: "7.1.0"
+ version: "8.0.0"
google_maps_flutter:
dependency: "direct main"
description:
name: google_maps_flutter
- sha256: acf0ec482d86b2ac55ade80597ce7f797a47971f5210ebfd030f0d58130e0a94
+ sha256: "1998c58100487af9c645ba05961e7eab8b20795611e67b1296311746a55037d4"
url: "https://pub.dev"
source: hosted
- version: "2.7.0"
+ version: "2.8.0"
google_maps_flutter_android:
dependency: "direct main"
description:
name: google_maps_flutter_android
- sha256: f6306d83edddba7aa017ca6f547d6f36a1443f90ed49d91d48ef70d7aa86e2e1
+ sha256: f34fec69957739245d732667429a6831f97419261f6f3c31cc6489eb3976444e
url: "https://pub.dev"
source: hosted
- version: "2.12.0"
+ version: "2.13.0"
google_maps_flutter_ios:
dependency: transitive
description:
name: google_maps_flutter_ios
- sha256: a6e3c6ecdda6c985053f944be13a0645ebb919da2ef0f5bc579c5e1670a5b2a8
+ sha256: bfa2583bfb2cf2bcd85c0d12366a2a4f38fff70d75bc1d089685677bd5d3acec
url: "https://pub.dev"
source: hosted
- version: "2.10.0"
+ version: "2.12.0"
google_maps_flutter_platform_interface:
dependency: "direct main"
description:
name: google_maps_flutter_platform_interface
- sha256: bd60ca330e3c7763b95b477054adec338a522d982af73ecc520b232474063ac5
+ sha256: "4f6930fd668bf5d40feb2695d5695dbc0c35e5542b557a34ad35be491686d2ba"
url: "https://pub.dev"
source: hosted
- version: "2.8.0"
+ version: "2.9.0"
google_maps_flutter_web:
dependency: transitive
description:
name: google_maps_flutter_web
- sha256: f3155c12119d8a5c2732fdf39ceb5cc095bc662059a03b4ea23294ecebe1d199
+ sha256: ff39211bd25d7fad125d19f757eba85bd154460907cd4d135e07e3d0f98a4130
url: "https://pub.dev"
source: hosted
- version: "0.5.8"
+ version: "0.5.10"
html:
dependency: transitive
description:
@@ -271,22 +271,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.19.0"
- js:
- dependency: transitive
- description:
- name: js
- sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
- url: "https://pub.dev"
- source: hosted
- version: "0.6.7"
- js_wrapping:
- dependency: transitive
- description:
- name: js_wrapping
- sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c
- url: "https://pub.dev"
- source: hosted
- version: "0.7.4"
latlong2:
dependency: "direct main"
description:
@@ -323,18 +307,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
mgrs_dart:
dependency: transitive
description:
@@ -464,26 +448,26 @@ packages:
dependency: transitive
description:
name: web
- sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
+ sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
url: "https://pub.dev"
source: hosted
- version: "0.5.1"
+ version: "1.0.0"
win32:
dependency: transitive
description:
name: win32
- sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
+ sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
url: "https://pub.dev"
source: hosted
- version: "5.5.1"
+ version: "5.5.3"
win32_registry:
dependency: transitive
description:
name: win32_registry
- sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb"
+ sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6"
url: "https://pub.dev"
source: hosted
- version: "1.1.3"
+ version: "1.1.4"
wkt_parser:
dependency: transitive
description:
@@ -493,5 +477,5 @@ packages:
source: hosted
version: "2.0.0"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
flutter: ">=3.22.0"
diff --git a/plugins/aves_services_google/pubspec.yaml b/plugins/aves_services_google/pubspec.yaml
index e502b7205..5c0953651 100644
--- a/plugins/aves_services_google/pubspec.yaml
+++ b/plugins/aves_services_google/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_services_none/pubspec.lock b/plugins/aves_services_none/pubspec.lock
index c7c23c52f..4aaf105a3 100644
--- a/plugins/aves_services_none/pubspec.lock
+++ b/plugins/aves_services_none/pubspec.lock
@@ -167,18 +167,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
mgrs_dart:
dependency: transitive
description:
@@ -297,5 +297,5 @@ packages:
source: hosted
version: "2.0.0"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
flutter: ">=3.10.0"
diff --git a/plugins/aves_services_none/pubspec.yaml b/plugins/aves_services_none/pubspec.yaml
index fef47c80c..0e1254e2e 100644
--- a/plugins/aves_services_none/pubspec.yaml
+++ b/plugins/aves_services_none/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_ui/pubspec.lock b/plugins/aves_ui/pubspec.lock
index 0ededd179..dc6c403f2 100644
--- a/plugins/aves_ui/pubspec.lock
+++ b/plugins/aves_ui/pubspec.lock
@@ -42,18 +42,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
sky_engine:
dependency: transitive
description: flutter
@@ -68,4 +68,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_ui/pubspec.yaml b/plugins/aves_ui/pubspec.yaml
index 88c5d1f0d..a231c2889 100644
--- a/plugins/aves_ui/pubspec.yaml
+++ b/plugins/aves_ui/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_utils/pubspec.lock b/plugins/aves_utils/pubspec.lock
index 94185daaa..045df08f7 100644
--- a/plugins/aves_utils/pubspec.lock
+++ b/plugins/aves_utils/pubspec.lock
@@ -42,18 +42,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
sky_engine:
dependency: transitive
description: flutter
@@ -68,4 +68,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_utils/pubspec.yaml b/plugins/aves_utils/pubspec.yaml
index 5dc41934b..98e953f71 100644
--- a/plugins/aves_utils/pubspec.yaml
+++ b/plugins/aves_utils/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_video/pubspec.lock b/plugins/aves_video/pubspec.lock
index abc78110d..678b686ce 100644
--- a/plugins/aves_video/pubspec.lock
+++ b/plugins/aves_video/pubspec.lock
@@ -64,18 +64,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
sky_engine:
dependency: transitive
description: flutter
@@ -90,4 +90,4 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
diff --git a/plugins/aves_video/pubspec.yaml b/plugins/aves_video/pubspec.yaml
index 7f92cb54d..0741f7602 100644
--- a/plugins/aves_video/pubspec.yaml
+++ b/plugins/aves_video/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_video_ffmpeg/pubspec.lock b/plugins/aves_video_ffmpeg/pubspec.lock
index 2a982a9a0..9ad53a0a8 100644
--- a/plugins/aves_video_ffmpeg/pubspec.lock
+++ b/plugins/aves_video_ffmpeg/pubspec.lock
@@ -88,18 +88,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
- sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.8.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
- version: "1.12.0"
+ version: "1.15.0"
plugin_platform_interface:
dependency: transitive
description:
@@ -122,5 +122,5 @@ packages:
source: hosted
version: "2.1.4"
sdks:
- dart: ">=3.4.1 <4.0.0"
+ dart: ">=3.5.0 <4.0.0"
flutter: ">=2.0.0"
diff --git a/plugins/aves_video_ffmpeg/pubspec.yaml b/plugins/aves_video_ffmpeg/pubspec.yaml
index 27bc082eb..195b26b5d 100644
--- a/plugins/aves_video_ffmpeg/pubspec.yaml
+++ b/plugins/aves_video_ffmpeg/pubspec.yaml
@@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none
environment:
- sdk: '>=3.4.1 <4.0.0'
+ sdk: '>=3.5.0 <4.0.0'
dependencies:
flutter:
diff --git a/plugins/aves_video_ijk/.gitignore b/plugins/aves_video_ijk/.gitignore
deleted file mode 100644
index 28124a571..000000000
--- a/plugins/aves_video_ijk/.gitignore
+++ /dev/null
@@ -1,30 +0,0 @@
-# Miscellaneous
-*.class
-*.log
-*.pyc
-*.swp
-.DS_Store
-.atom/
-.buildlog/
-.history
-.svn/
-migrate_working_dir/
-
-# IntelliJ related
-*.iml
-*.ipr
-*.iws
-.idea/
-
-# The .vscode folder contains launch configuration and tasks you configure in
-# VS Code which you may wish to be included in version control, so this line
-# is commented out by default.
-#.vscode/
-
-# Flutter/Dart/Pub related
-# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
-#/pubspec.lock
-**/doc/api/
-.dart_tool/
-.packages
-build/
diff --git a/plugins/aves_video_ijk/.metadata b/plugins/aves_video_ijk/.metadata
deleted file mode 100644
index 9596faeed..000000000
--- a/plugins/aves_video_ijk/.metadata
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: 796c8ef79279f9c774545b3771238c3098dbefab
- channel: stable
-
-project_type: package
diff --git a/plugins/aves_video_ijk/analysis_options.yaml b/plugins/aves_video_ijk/analysis_options.yaml
deleted file mode 100644
index f04c6cf0f..000000000
--- a/plugins/aves_video_ijk/analysis_options.yaml
+++ /dev/null
@@ -1 +0,0 @@
-include: ../../analysis_options.yaml
diff --git a/plugins/aves_video_ijk/lib/aves_video_ijk.dart b/plugins/aves_video_ijk/lib/aves_video_ijk.dart
deleted file mode 100644
index 4488c8daa..000000000
--- a/plugins/aves_video_ijk/lib/aves_video_ijk.dart
+++ /dev/null
@@ -1,5 +0,0 @@
-library aves_video_ijk;
-
-export 'src/controller.dart';
-export 'src/factory.dart';
-export 'src/metadata.dart';
diff --git a/plugins/aves_video_ijk/lib/src/controller.dart b/plugins/aves_video_ijk/lib/src/controller.dart
deleted file mode 100644
index 0b594406d..000000000
--- a/plugins/aves_video_ijk/lib/src/controller.dart
+++ /dev/null
@@ -1,593 +0,0 @@
-import 'dart:async';
-
-import 'package:aves_model/aves_model.dart';
-import 'package:aves_utils/aves_utils.dart';
-import 'package:aves_video/aves_video.dart';
-import 'package:collection/collection.dart';
-import 'package:fijkplayer/fijkplayer.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-
-class IjkVideoController extends AvesVideoController {
- final EventChannel _eventChannel = const OptionalEventChannel('befovy.com/fijk/event');
-
- late FijkPlayer _instance;
- final List _subscriptions = [];
- final StreamController _valueStreamController = StreamController.broadcast();
- final StreamController _timedTextStreamController = StreamController.broadcast();
- final StreamController _volumeStreamController = StreamController.broadcast();
- final StreamController _speedStreamController = StreamController.broadcast();
- final AChangeNotifier _completedNotifier = AChangeNotifier();
- Offset _macroBlockCrop = Offset.zero;
- Timer? _initialPlayTimer;
- double _speed = 1;
- double _volume = 1;
-
- // audio/video get out of sync with speed < .5
- // the video stream plays at .5 but the audio is slowed as requested
- @override
- final double minSpeed = .5;
-
- // ijkplayer configures `AudioTrack` buffer for a maximum speed of 2
- // but `SoundTouch` can go higher
- @override
- final double maxSpeed = 2;
-
- @override
- final ValueNotifier canCaptureFrameNotifier = ValueNotifier(false);
-
- @override
- final ValueNotifier canMuteNotifier = ValueNotifier(false);
-
- @override
- final ValueNotifier canSetSpeedNotifier = ValueNotifier(false);
-
- @override
- final ValueNotifier canSelectStreamNotifier = ValueNotifier(false);
-
- @override
- final ValueNotifier sarNotifier = ValueNotifier(null);
-
- Stream get _valueStream => _valueStreamController.stream;
-
- static const initialPlayDelay = Duration(milliseconds: 100);
- static const gifLikeVideoDurationThreshold = Duration(seconds: 10);
- static const gifLikeBitRateThreshold = 2 << 18; // 512kB/s (4Mb/s)
- static const captureFrameEnabled = true;
-
- IjkVideoController(
- super.entry, {
- required super.playbackStateHandler,
- required super.settings,
- }) {
- _instance = FijkPlayer();
- _valueStream.map((value) => value.videoRenderStart).firstWhere((v) => v, orElse: () => false).then(
- (started) {
- canCaptureFrameNotifier.value = captureFrameEnabled && started;
- },
- onError: (error) {},
- );
- _valueStream.map((value) => value.audioRenderStart).firstWhere((v) => v, orElse: () => false).then(
- (started) {
- canMuteNotifier.value = started;
- canSetSpeedNotifier.value = started;
- },
- onError: (error) {},
- );
- _startListening();
- }
-
- @override
- Future dispose() async {
- await super.dispose();
-
- _initialPlayTimer?.cancel();
- _stopListening();
- await _valueStreamController.close();
- await _timedTextStreamController.close();
- await _instance.release();
-
- _completedNotifier.dispose();
- canCaptureFrameNotifier.dispose();
- canMuteNotifier.dispose();
- canSetSpeedNotifier.dispose();
- canSelectStreamNotifier.dispose();
- sarNotifier.dispose();
- }
-
- void _startListening() {
- _instance.addListener(_onValueChanged);
- _subscriptions.add(_eventChannel.receiveBroadcastStream().listen((event) => _onPluginEvent(event as Map?)));
- _subscriptions.add(_valueStream.where((value) => value.state == FijkState.completed).listen((_) => _completedNotifier.notify()));
- _subscriptions.add(_instance.onTimedText.listen(_timedTextStreamController.add));
- _subscriptions.add(settings.updateStream
- .where((event) => {
- SettingKeys.enableVideoHardwareAccelerationKey,
- SettingKeys.videoLoopModeKey,
- }.contains(event.key))
- .listen((_) => _instance.reset()));
- }
-
- void _stopListening() {
- _instance.removeListener(_onValueChanged);
- _subscriptions
- ..forEach((sub) => sub.cancel())
- ..clear();
- }
-
- Future _init({int startMillis = 0}) async {
- if (isReady) {
- _stopListening();
- await _instance.release();
- _instance = FijkPlayer();
- _startListening();
- }
-
- sarNotifier.value = null;
- streams.clear();
- _applyOptions(startMillis);
-
- // calling `setDataSource()` with `autoPlay` starts as soon as possible, but often yields initial artifacts
- // so we introduce a small delay after the player is declared `prepared`, before playing
- await _instance.setDataSourceUntilPrepared(entry.uri);
- await _applyVolume();
- if (speed != 1) {
- await _applySpeed();
- }
- _initialPlayTimer = Timer(initialPlayDelay, play);
- }
-
- void _applyOptions(int startMillis) {
- // FFmpeg options
- // `setHostOption`, cf:
- // - https://fijkplayer.befovy.com/docs/zh/host-option.html
- // - https://github.com/deckerst/fijkplayer/blob/master/android/src/main/java/com/befovy/fijkplayer/HostOption.java
- // `setFormatOption`, cf https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/options_table.h
- // `setCodecOption`, cf https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/options_table.h
- // `setSwsOption`, cf https://github.com/FFmpeg/FFmpeg/blob/master/libswscale/options.c
- // `setPlayerOption`, cf https://github.com/Bilibili/ijkplayer/blob/master/ijkmedia/ijkplayer/ff_ffplay_options.h
- // cf https://www.jianshu.com/p/843c86a9e9ad
- // cf https://www.jianshu.com/p/3649c073b346
-
- final options = FijkOption();
-
- // when accurate seek is enabled and seeking fails, it takes time (cf `accurate-seek-timeout`) to acknowledge the error and proceed
- // failure seems to happen when pause-seeking videos with an audio stream, whatever container or video stream
- // player cannot be dynamically set to use accurate seek only when playing
- const accurateSeekEnabled = false;
-
- // playing with HW acceleration seems to skip the last frames of some videos
- // so HW acceleration is always disabled for GIF-like videos where the last frames may be significant
- final hwAccelerationEnabled = settings.enableVideoHardwareAcceleration && !_isGifLike();
-
- // TODO TLAD [video] flaky: HW codecs sometimes fail when seek-starting some videos, e.g. MP2TS/h264(HDPR)
- if (hwAccelerationEnabled) {
- // when HW acceleration is enabled, videos with dimensions that do not fit 16x macroblocks need cropping
- // TODO TLAD [video] flaky: not all formats/devices need this correction, e.g. 498x278 MP4 on S7, 408x244 WEBM on S10e do not
- final s = entry.displaySize % 16 * -1 % 16;
- _macroBlockCrop = Offset(s.width, s.height);
- }
-
- final loopEnabled = settings.videoLoopMode.shouldLoop(entry);
-
- // `fastseek`: enable fast, but inaccurate seeks for some formats
- // in practice the flag seems ineffective, but harmless too
- options.setFormatOption('fflags', 'fastseek');
-
- // `enable-snapshot`: enable snapshot interface
- // default: 0, in [0, 1]
- // there is a performance cost, and it should be set up before playing
- options.setHostOption('enable-snapshot', captureFrameEnabled ? 1 : 0);
-
- // default: 0, in [0, 1]
- options.setHostOption('request-audio-focus', 1);
-
- // default: 0, in [0, 1]
- options.setHostOption('release-audio-focus', 1);
-
- // `accurate-seek-timeout`: accurate seek timeout
- // default: 5000 ms, in [0, 5000]
- options.setPlayerOption('accurate-seek-timeout', 1000);
-
- // `cover-after-prepared`: show cover provided to `FijkView` when player is `prepared` without auto play
- // default: 0, in [0, 1]
- options.setPlayerOption('cover-after-prepared', 0);
-
- // `enable-accurate-seek`: enable accurate seek
- // default: 0, in [0, 1]
- // ignore: dead_code
- options.setPlayerOption('enable-accurate-seek', accurateSeekEnabled ? 1 : 0);
-
- // `min-frames`: minimal frames to stop pre-reading
- // default: 50000, in [2, 50000]
- // a comment in `IjkMediaPlayer.java` recommends setting this to 25 when de/selecting streams
- options.setPlayerOption('min-frames', 25);
-
- // `framedrop`: drop frames when cpu is too slow
- // default: 0, in [-1, 120]
- options.setPlayerOption('framedrop', 5);
-
- // `loop`: set number of times the playback shall be looped
- // default: 1, in [INT_MIN, INT_MAX]
- options.setPlayerOption('loop', loopEnabled ? -1 : 1);
-
- // `mediacodec-all-videos`: MediaCodec: enable all videos
- // default: 0, in [0, 1]
- options.setPlayerOption('mediacodec-all-videos', hwAccelerationEnabled ? 1 : 0);
-
- // `seek-at-start`: set offset of player should be seeked
- // default: 0, in [0, INT_MAX]
- options.setPlayerOption('seek-at-start', startMillis);
-
- // `soundtouch`: enable SoundTouch
- // default: 0, in [0, 1]
- // `SoundTouch` cannot be enabled/disabled after video is `prepared`
- // When `SoundTouch` is enabled:
- // - slowed down videos have a weird wobbly audio
- // - we can set speeds higher than the `AudioTrack` limit of 2
- options.setPlayerOption('soundtouch', _needSoundTouch(speed) ? 1 : 0);
-
- // `subtitle`: decode subtitle stream
- // default: 0, in [0, 1]
- options.setPlayerOption('subtitle', 1);
-
- _instance.applyOptions(options);
- }
-
- bool _isGifLike() {
- // short
- final durationSecs = (entry.durationMillis ?? 0) ~/ 1000;
- if (durationSecs == 0) return false;
- if (durationSecs > gifLikeVideoDurationThreshold.inSeconds) return false;
-
- // light
- final sizeBytes = entry.sizeBytes;
- if (sizeBytes == null) return false;
- if (sizeBytes / durationSecs > gifLikeBitRateThreshold) return false;
-
- return true;
- }
-
- // cf https://developer.android.com/reference/android/media/AudioManager
- static const int _audioFocusLoss = -1;
- static const int _audioFocusRequestFailed = 0;
-
- void _onPluginEvent(Map? fields) {
- if (fields == null) return;
- final event = fields['event'] as String?;
- switch (event) {
- case 'audiofocus':
- final value = fields['value'] as int?;
- if (value != null) {
- switch (value) {
- case _audioFocusLoss:
- case _audioFocusRequestFailed:
- pause();
- }
- }
- case 'volume':
- // ignore
- break;
- }
- }
-
- void _onValueChanged() {
- if (_instance.state == FijkState.prepared && streams.isEmpty) {
- _fetchStreams();
- }
- _valueStreamController.add(_instance.value);
- }
-
- @override
- void onVisualChanged() => _init(startMillis: currentPosition);
-
- @override
- Future play() async {
- if (isReady) {
- await _instance.start();
- } else {
- await _init();
- }
- }
-
- @override
- Future pause() async {
- if (isReady) {
- _initialPlayTimer?.cancel();
- await _instance.pause();
- }
- }
-
- @override
- Future seekTo(int targetMillis) async {
- targetMillis = targetMillis.clamp(0, duration);
- if (isReady) {
- await _instance.seekTo(targetMillis);
- } else {
- // always start playing, even when seeking on uninitialized player, otherwise the texture is not updated
- // as a workaround, pausing after a brief duration is possible, but fiddly
- await _init(startMillis: targetMillis);
- }
- }
-
- @override
- Listenable get playCompletedListenable => _completedNotifier;
-
- @override
- VideoStatus get status => _instance.state.toAves;
-
- @override
- Stream get statusStream => _valueStream.map((value) => value.state.toAves);
-
- @override
- Stream get volumeStream => _volumeStreamController.stream;
-
- @override
- Stream get speedStream => _speedStreamController.stream;
-
- @override
- bool get isReady => _instance.isPlayable();
-
- @override
- int get duration {
- final controllerDuration = _instance.value.duration.inMilliseconds;
- // use expected duration when controller duration is not set yet
- return controllerDuration == 0 ? (entry.durationMillis ?? 0) : controllerDuration;
- }
-
- @override
- int get currentPosition => _instance.currentPos.inMilliseconds;
-
- @override
- Stream get positionStream => _instance.onCurrentPosUpdate.map((pos) => pos.inMilliseconds);
-
- @override
- Stream get timedTextStream => _timedTextStreamController.stream;
-
- @override
- bool get isMuted => _volume == 0;
-
- @override
- Future mute(bool muted) async {
- _volume = muted ? 0 : 1;
- _volumeStreamController.add(_volume);
- await _applyVolume();
- }
-
- Future