Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2024-08-07 22:01:05 +02:00
commit 00681cbaee
208 changed files with 2132 additions and 1877 deletions

@ -1 +1 @@
Subproject commit b0850beeb25f6d5b10426284f506557f66181b36
Subproject commit 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819

View file

@ -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

View file

@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
## <a id="v1.11.9"></a>[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
## <a id="v1.11.8"></a>[v1.11.8] - 2024-07-19
### Added

View file

@ -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')

View file

@ -323,8 +323,10 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- as of Flutter v3.22.0 (stable),
Impeller fails to render videos (via `ffmpegkit`), and has random glitches -->
<!--
Impeller is not supported by `media_kit` v1.1.10+1:
https://github.com/media-kit/media-kit/issues/707
-->
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />

View file

@ -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,14 +120,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
initFlutterEngine(context)
val messenger = flutterEngine!!.dartExecutor
val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
try {
val props = suspendCoroutine<Any?> { cont ->
defaultScope.launch {
FlutterUtils.runOnUiThread {
channel.invokeMethod("drawWidget", hashMapOf(
val params = hashMapOf(
"widgetId" to widgetId,
"sizesDip" to sizesDip,
"devicePixelRatio" to getDevicePixelRatio(),
@ -126,19 +131,14 @@ class HomeWidgetProvider : AppWidgetProvider() {
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"))
}
})
initFlutterEngine(context)
try {
val props = suspendCoroutine { cont ->
defaultScope.launch {
FlutterUtils.runOnUiThread {
tryDrawWidget(params, cont, 0)
}
}
}
@ -150,6 +150,30 @@ class HomeWidgetProvider : AppWidgetProvider() {
return null
}
private fun tryDrawWidget(params: HashMap<String, Any>, cont: Continuation<Any?>, 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<HomeWidgetProvider>()
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

View file

@ -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) {
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
}

View file

@ -6,7 +6,7 @@
<string name="search_shortcut_short_label">Zoeken</string>
<string name="videos_shortcut_short_label">Videos</string>
<string name="analysis_channel_name">Media indexeren</string>
<string name="analysis_notification_default_title">Indexeren van media</string>
<string name="analysis_notification_default_title">Media indexeren</string>
<string name="analysis_notification_action_stop">Stoppen</string>
<string name="safe_mode_shortcut_short_label">Veilige modus</string>
</resources>

View file

@ -7,6 +7,6 @@
<string name="videos_shortcut_short_label">Videor</string>
<string name="analysis_channel_name">Media scanning</string>
<string name="analysis_notification_default_title">Scannar media</string>
<string name="analysis_notification_action_stop">Stop</string>
<string name="analysis_notification_action_stop">Stopp</string>
<string name="search_shortcut_short_label">Sök</string>
</resources>

View file

@ -1,4 +0,0 @@
In v1.11.1:
- watch videos with SRT subtitle files
- enjoy the app in Persian
Full changelog available on GitHub

View file

@ -1,4 +0,0 @@
In v1.11.1:
- watch videos with SRT subtitle files
- enjoy the app in Persian
Full changelog available on GitHub

View file

@ -1,3 +0,0 @@
In v1.11.2:
- show selected albums together in Collection
Full changelog available on GitHub

View file

@ -1,3 +0,0 @@
In v1.11.2:
- show selected albums together in Collection
Full changelog available on GitHub

View file

@ -1,3 +0,0 @@
In v1.11.3:
- show selected albums together in Collection
Full changelog available on GitHub

View file

@ -1,3 +0,0 @@
In v1.11.3:
- show selected albums together in Collection
Full changelog available on GitHub

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1536,5 +1536,17 @@
"chipActionGoToExplorerPage": "عرض في المستكشف",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "المستكشف",
"@explorerPageTitle": {}
"@explorerPageTitle": {},
"explorerActionSelectStorageVolume": "حدد التخزين",
"@explorerActionSelectStorageVolume": {},
"setHomeCustom": "مخصص",
"@setHomeCustom": {},
"selectStorageVolumeDialogTitle": "حدد التَخزين",
"@selectStorageVolumeDialogTitle": {},
"sortOrderShortestFirst": "الأقصر أولاً",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "الأطول أولاً",
"@sortOrderLongestFirst": {},
"sortByDuration": "حسب المدة",
"@sortByDuration": {}
}

View file

@ -1536,5 +1536,17 @@
"chipActionGoToExplorerPage": "Паказаць у Правадыру",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Правадыр",
"@explorerPageTitle": {}
"@explorerPageTitle": {},
"sortByDuration": "Па працягласці",
"@sortByDuration": {},
"sortOrderShortestFirst": "Спачатку самы кароткі",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "Спачатку самы доўгі",
"@sortOrderLongestFirst": {},
"explorerActionSelectStorageVolume": "Выбраць сховішча",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Выбраць сховішча",
"@selectStorageVolumeDialogTitle": {},
"setHomeCustom": "Па-свойму",
"@setHomeCustom": {}
}

View file

@ -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",

View file

@ -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": {}
}

View file

@ -1384,5 +1384,11 @@
"explorerActionSelectStorageVolume": "Choisir le stockage",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Volumes de stockage",
"@selectStorageVolumeDialogTitle": {}
"@selectStorageVolumeDialogTitle": {},
"sortByDuration": "par durée",
"@sortByDuration": {},
"sortOrderShortestFirst": "Plus courts dabord",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "Plus longs dabord",
"@sortOrderLongestFirst": {}
}

View file

@ -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": {}
}

View file

@ -1374,5 +1374,11 @@
"renameProcessorHash": "Hash",
"@renameProcessorHash": {},
"chipActionShowCollection": "Tampilkan di Koleksi",
"@chipActionShowCollection": {}
"@chipActionShowCollection": {},
"explorerActionSelectStorageVolume": "Pilih penyimpanan",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Pilih Penyimpanan",
"@selectStorageVolumeDialogTitle": {},
"setHomeCustom": "Kustom",
"@setHomeCustom": {}
}

View file

@ -1384,5 +1384,11 @@
"explorerActionSelectStorageVolume": "저장공간 선택",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "저장공간",
"@selectStorageVolumeDialogTitle": {}
"@selectStorageVolumeDialogTitle": {},
"sortByDuration": "길이",
"@sortByDuration": {},
"sortOrderShortestFirst": "짧은 순",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "긴 순",
"@sortOrderLongestFirst": {}
}

View file

@ -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": "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 fotos en videos 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 fotos en videos 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} paginas 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": "Fotos en videos in deze mappen, of een van hun submappen, verschijnen niet in je verzameling.",
"settingsHiddenPathsBanner": "Fotos en videos 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": {}
}

View file

@ -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": {}
}

View file

@ -1380,5 +1380,15 @@
"explorerPageTitle": "Проводник",
"@explorerPageTitle": {},
"explorerActionSelectStorageVolume": "Выбрать хранилище",
"@explorerActionSelectStorageVolume": {}
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Выбрать хранилище",
"@selectStorageVolumeDialogTitle": {},
"sortByDuration": "По продолжительности",
"@sortByDuration": {},
"sortOrderLongestFirst": "Сначала самый длинный",
"@sortOrderLongestFirst": {},
"setHomeCustom": "По своему",
"@setHomeCustom": {},
"sortOrderShortestFirst": "Сначала самый короткий",
"@sortOrderShortestFirst": {}
}

View file

@ -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": {}
}

View file

@ -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": {}
}

View file

@ -1542,5 +1542,11 @@
"explorerActionSelectStorageVolume": "Обрати сховище",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Оберіть сховище",
"@selectStorageVolumeDialogTitle": {}
"@selectStorageVolumeDialogTitle": {},
"sortByDuration": "За тривалістю",
"@sortByDuration": {},
"sortOrderShortestFirst": "Спершу найкоротше",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "Спершу найдовше",
"@sortOrderLongestFirst": {}
}

View file

@ -1378,5 +1378,17 @@
"explorerPageTitle": "资源管理器",
"@explorerPageTitle": {},
"chipActionGoToExplorerPage": "在资源管理器中显示",
"@chipActionGoToExplorerPage": {}
"@chipActionGoToExplorerPage": {},
"setHomeCustom": "自定义",
"@setHomeCustom": {},
"explorerActionSelectStorageVolume": "选择存储器",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "选择存储器",
"@selectStorageVolumeDialogTitle": {},
"sortByDuration": "按时长",
"@sortByDuration": {},
"sortOrderLongestFirst": "先长后短",
"@sortOrderLongestFirst": {},
"sortOrderShortestFirst": "先短后长",
"@sortOrderShortestFirst": {}
}

View file

@ -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/*

View file

@ -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
};
}

View file

@ -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,

View file

@ -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';

View file

@ -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;

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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);
}
}

View file

@ -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';

View file

@ -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';

View file

@ -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<String> get recentDestinationAlbums => getStringList(SettingKeys.recentDestinationAlbumsKey) ?? [];
set recentDestinationAlbums(List<String> newValue) => set(SettingKeys.recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList());
set recentDestinationAlbums(List<String> newValue) => set(SettingKeys.recentDestinationAlbumsKey, newValue.take(recentFilterHistoryMax).toList());
List<CollectionFilter> get recentTags => (getStringList(SettingKeys.recentTagsKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
set recentTags(List<CollectionFilter> newValue) => set(SettingKeys.recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
set recentTags(List<CollectionFilter> newValue) => set(SettingKeys.recentTagsKey, newValue.take(recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
}

View file

@ -9,17 +9,18 @@ mixin PrivacySettings on SettingsAccess, SearchSettings {
set hiddenFilters(Set<CollectionFilter> newValue) => set(SettingKeys.hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList());
void changeFilterVisibility(Set<CollectionFilter> 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;
}
}

View file

@ -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<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
case EntrySortFactor.size:
case EntrySortFactor.duration:
sections = Map.fromEntries([
MapEntry(const SectionKey(), _filteredSortedEntries),
]);

View file

@ -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';

View file

@ -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(<String, dynamic>{

View file

@ -80,11 +80,6 @@ class PlatformMetadataEditService implements MetadataEditService {
Map<MetadataType, dynamic> 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', <String, dynamic>{
'entry': entry.toPlatformEntryMap(),

View file

@ -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<CatalogMetadata?> 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<CatalogMetadata?> call() async {
try {
// returns map with:

View file

@ -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);

View file

@ -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;

View file

@ -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<String> videoCapturesPaths;
Set<StorageVolume> storageVolumes = {};
bool _initialized = false;
_State _initialized = _State.uninitialized;
AndroidFileUtils._private();
Future<void> init() async {
if (_initialized) return;
if (_initialized == _State.uninitialized) {
_initialized = _State.initializing;
await _doInit();
_initialized = _State.initialized;
}
}
Future<void> _doInit() async {
separator = pContext.separator;
await _initStorageVolumes();
vaultRoot = await storageService.getVaultRoot();
@ -50,8 +58,6 @@ class AndroidFileUtils {
// from Aves
avesVideoCapturesPath,
};
_initialized = true;
}
Future<void> _initStorageVolumes() async {

View file

@ -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,
};
}
}

View file

@ -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,
};

View file

@ -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,
};
}
}

View file

@ -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';

View file

@ -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();

View file

@ -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<AvesApp> with WidgetsBindingObserver {
child: ValueListenableBuilder<PageTransitionsBuilder>(
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<AvesApp> with WidgetsBindingObserver {
void didHaveMemoryPressure() {
super.didHaveMemoryPressure();
reportService.log('App memory pressure');
imageCache.clear();
}
@override
@ -631,7 +634,7 @@ class _AvesAppState extends State<AvesApp> 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<AvesApp> 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;
}
}

View file

@ -79,6 +79,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
EntrySortFactor.size,
EntrySortFactor.name,
EntrySortFactor.rating,
EntrySortFactor.duration,
];
static const _groupOptions = [
@ -94,6 +95,11 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
TileLayout.list,
];
static const _trashSelectionQuickActions = [
EntrySetAction.delete,
EntrySetAction.restore,
];
@override
void initState() {
super.initState();
@ -388,7 +394,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> 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<CollectionAppBar> 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)),
],
),
];

View file

@ -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;

View file

@ -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,
];
}
},
);

View file

@ -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<CollectionFilter> filters;
final bool interactive;
final FilterCallback? onTap, onRemove;
final AFilterCallback? onTap, onRemove;
FilterBar({
super.key,
@ -45,7 +47,7 @@ class _FilterBarState extends State<FilterBar> {
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<Settings>().animate && _userTappedFilter == filter;
listState!.removeItem(
index,
animate
@ -123,7 +125,7 @@ class _FilterBarState extends State<FilterBar> {
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,

View file

@ -66,6 +66,7 @@ class CollectionSectionHeader extends StatelessWidget {
selectable: selectable,
);
case EntrySortFactor.size:
case EntrySortFactor.duration:
break;
}
return null;

View file

@ -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<String> {
final ValueNotifier<String?> valueNotifier;
@override
final List<String> options;
final bool blurred;
final PopupMenuPosition chooserPosition;
@ -25,7 +28,6 @@ class AlbumQuickChooser extends StatelessWidget {
@override
Widget build(BuildContext context) {
final source = context.read<CollectionSource>();
return MenuQuickChooser<String>(
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<CollectionSource>();
return AlbumFilter(option, source.getAlbumDisplayName(context, option));
}
}

View file

@ -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<T> extends StatefulWidget {
final bool blurred;
final PopupMenuPosition chooserPosition;
final Stream<Offset> 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<T> 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<MenuQuickChooser<T>> createState() => _MenuQuickChooserState<T>();
@ -38,6 +47,10 @@ class MenuQuickChooser<T> extends StatefulWidget {
class _MenuQuickChooserState<T> extends State<MenuQuickChooser<T>> {
final List<StreamSubscription> _subscriptions = [];
final ValueNotifier<Rect> _selectedRowRect = ValueNotifier(Rect.zero);
final ScrollController _scrollController = ScrollController();
int _scrollDirection = 0;
Timer? _scrollUpdateTimer;
Offset _globalPosition = Offset.zero;
ValueNotifier<T?> get valueNotifier => widget.valueNotifier;
@ -45,12 +58,26 @@ class _MenuQuickChooserState<T> extends State<MenuQuickChooser<T>> {
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<T> extends State<MenuQuickChooser<T>> {
@override
void dispose() {
_unregisterWidget(widget);
_selectedRowRect.dispose();
_scrollController.dispose();
_scrollUpdateTimer?.cancel();
super.dispose();
}
@ -91,28 +121,11 @@ class _MenuQuickChooserState<T> extends State<MenuQuickChooser<T>> {
builder: (context, selectedValue, child) {
final durations = context.watch<DurationsData>();
List<Widget> 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<T> extends State<MenuQuickChooser<T>> {
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<T> extends State<MenuQuickChooser<T>> {
);
}
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));
}
}
}

View file

@ -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<T> {
List<T> 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);
}
}

View file

@ -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<MoveButton, String> {
final source = context.read<CollectionSource>();
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();

View file

@ -51,7 +51,6 @@ class _ShareButtonState extends ChooserQuickButtonState<ShareButton, ShareAction
child: ShareQuickChooser(
valueNotifier: chooserValueNotifier,
options: options,
autoReverse: false,
blurred: widget.blurred,
chooserPosition: chooserPosition,
pointerGlobalPosition: pointerGlobalPosition,

View file

@ -1,24 +1,26 @@
import 'dart:async';
import 'dart:math';
import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ShareQuickChooser extends StatelessWidget {
final ValueNotifier<ShareAction?> valueNotifier;
final List<ShareAction> options;
final bool autoReverse;
final bool blurred;
final PopupMenuPosition chooserPosition;
final Stream<Offset> 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<ShareAction>(
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),
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);
}
}

View file

@ -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<TagButton, CollectionFilte
@override
Widget buildChooser(Animation<double> 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<CollectionSource>();
final filters = source.sortedTags.map(TagFilter.new).whereNot(options.contains).toSet();

View file

@ -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<CollectionFilter> {
final ValueNotifier<CollectionFilter?> valueNotifier;
@override
final List<CollectionFilter> 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;
}

View file

@ -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<FavouriteToggler> {
icon: const Icon(isNotFavouriteIcon),
);
}
final animate = context.select<Settings, bool>((v) => v.animate);
return Stack(
alignment: Alignment.center,
children: [
@ -82,6 +84,7 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
focusNode: widget.focusNode,
tooltip: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite,
),
if (animate)
Sweeper(
key: ValueKey(entries.length == 1 ? entries.first : entries.length),
builder: (context) => Icon(

View file

@ -95,9 +95,9 @@ class _PlayTogglerState extends State<PlayToggler> 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();
}
}

View file

@ -24,6 +24,7 @@ typedef MarginComputer = EdgeInsets Function(BuildContext context);
mixin FeedbackMixin {
static final ValueNotifier<MarginComputer?> 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<MarginComputer?>(
@ -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),
),
),
),

View file

@ -135,8 +135,8 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
super.dispose();
}
void _onAnimationStatusChanged(AnimationStatus animationStatus) {
if (animationStatus == AnimationStatus.completed) {
void _onAnimationStatusChanged(AnimationStatus status) {
if (status.isCompleted) {
if (widget.onVisible != null && !_wasVisible) {
widget.onVisible!();
}

View file

@ -1,37 +0,0 @@
import 'package:flutter/material.dart';
class AvesPopupMenuButton<T> extends PopupMenuButton<T> {
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<T> createState() => _AvesPopupMenuButtonState<T>();
}
class _AvesPopupMenuButtonState<T> extends PopupMenuButtonState<T> {
@override
void showButtonMenu() {
(widget as AvesPopupMenuButton).onMenuOpened?.call();
super.showButtonMenu();
}
}

View file

@ -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,

View file

@ -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);
}

View file

@ -12,7 +12,7 @@ class TitledExpandableFilterRow extends StatelessWidget {
final ValueNotifier<String?> 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;

View file

@ -105,7 +105,7 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
void _onAnimationStatusChanged(AnimationStatus status) {
setState(() {});
if (status == AnimationStatus.completed) {
if (status.isCompleted) {
widget.onSweepEnd?.call();
}
}

View file

@ -189,6 +189,7 @@ class _SectionSelectableLeading<T> extends StatelessWidget {
Widget build(BuildContext context) {
if (!selectable) return _buildBrowsing(context);
final duration = context.select<DurationsData, Duration>((v) => v.formTransition);
final isSelecting = context.select<Selection<T>, bool>((selection) => selection.isSelecting);
final Widget child = isSelecting
? _SectionSelectingLeading<T>(
@ -201,7 +202,7 @@ class _SectionSelectableLeading<T> 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<T> extends StatelessWidget {
@override
Widget build(BuildContext context) {
final duration = context.select<DurationsData, Duration>((v) => v.formTransition);
final sectionEntries = context.watch<SectionedListLayout<T>>().sections[sectionKey] ?? [];
final selection = context.watch<Selection<T>>();
final isSelected = selection.isSelected(sectionEntries);
return AnimatedSwitcher(
duration: ADurations.sectionHeaderAnimation,
duration: duration,
switchInCurve: Curves.easeOutBack,
switchOutCurve: Curves.easeOutBack,
transitionBuilder: (child, animation) => ScaleTransition(

View file

@ -10,8 +10,6 @@ class GridItemSelectionOverlay<T> 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<T> extends StatelessWidget {
@override
Widget build(BuildContext context) {
final duration = context.select<DurationsData, Duration>((v) => v.formTransition);
final isSelecting = context.select<Selection<T>, bool>((selection) => selection.isSelecting);
return AnimatedSwitcher(
duration: duration,

View file

@ -55,9 +55,9 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
return scrollableBox.size.width;
}
static const double scrollEdgeRatio = .15;
static const double scrollMaxPixelPerSecond = 600.0;
static const Duration scrollUpdateInterval = Duration(milliseconds: 100);
static const double _scrollEdgeRatio = .15;
static const double _scrollMaxPixelPerSecond = 600.0;
static const Duration _scrollUpdateInterval = Duration(milliseconds: 100);
@override
void initState() {
@ -158,7 +158,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
final top = dy < height / 2;
final distanceToEdge = max(0, top ? dy - _scrollableInsets.top : height - dy - _scrollableInsets.bottom);
final threshold = height * scrollEdgeRatio;
final threshold = height * _scrollEdgeRatio;
if (distanceToEdge < threshold) {
_setScrollSpeed((top ? -1 : 1) * roundToPrecision((threshold - distanceToEdge) / threshold, decimals: 1));
} else {
@ -185,7 +185,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
final target = speedFactor > 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<T> extends State<GridSelectionGestureDe
);
// use a timer to update the selection, because `onLongPressMoveUpdate`
// is not called when the pointer stays still while the view is scrolling
_selectionUpdateTimer = Timer.periodic(scrollUpdateInterval, (_) => _onLongPressUpdate());
_selectionUpdateTimer = Timer.periodic(_scrollUpdateInterval, (_) => _onLongPressUpdate());
}
}

View file

@ -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<Settings, bool>((v) => v.animate);
if (animate) {
_title = Hero(
tag: titleHeroTag,
flightShuttleBuilder: _flightShuttleBuilder,
transitionOnUserGestures: true,
child: AnimatedSwitcher(
duration: context.read<DurationsData>().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<DurationsData>().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,
),
),
],

View file

@ -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<AvesFilterChip> {
maxWidth: max(
AvesFilterChip.minChipWidth,
widget.maxWidth ??
AvesFilterChip.computeMaxWidth(
AvesFilterChip.computeMaxWidthForRow(
context,
minChipPerRow: 2,
chipPadding: FilterBar.chipPadding.horizontal,

View file

@ -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),
],
),
),
],
),

Some files were not shown because too many files have changed in this diff Show more