Merge branch 'develop'
2
.github/workflows/check.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
# Available versions may lag behind https://github.com/flutter/flutter.git
|
# Available versions may lag behind https://github.com/flutter/flutter.git
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: '3.3.2'
|
flutter-version: '3.3.4'
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
|
|
||||||
- name: Clone the repository.
|
- name: Clone the repository.
|
||||||
|
|
10
.github/workflows/release.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
# Available versions may lag behind https://github.com/flutter/flutter.git
|
# Available versions may lag behind https://github.com/flutter/flutter.git
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: '3.3.2'
|
flutter-version: '3.3.4'
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
|
|
||||||
# Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1):
|
# Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1):
|
||||||
|
@ -56,15 +56,15 @@ jobs:
|
||||||
rm release.keystore.asc
|
rm release.keystore.asc
|
||||||
mkdir outputs
|
mkdir outputs
|
||||||
(cd scripts/; ./apply_flavor_play.sh)
|
(cd scripts/; ./apply_flavor_play.sh)
|
||||||
flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.3.2.sksl.json
|
flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.3.4.sksl.json
|
||||||
cp build/app/outputs/bundle/playRelease/*.aab outputs
|
cp build/app/outputs/bundle/playRelease/*.aab outputs
|
||||||
flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.3.2.sksl.json
|
flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.3.4.sksl.json
|
||||||
cp build/app/outputs/apk/play/release/*.apk outputs
|
cp build/app/outputs/apk/play/release/*.apk outputs
|
||||||
(cd scripts/; ./apply_flavor_huawei.sh)
|
(cd scripts/; ./apply_flavor_huawei.sh)
|
||||||
flutter build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders_3.3.2.sksl.json
|
flutter build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders_3.3.4.sksl.json
|
||||||
cp build/app/outputs/apk/huawei/release/*.apk outputs
|
cp build/app/outputs/apk/huawei/release/*.apk outputs
|
||||||
(cd scripts/; ./apply_flavor_izzy.sh)
|
(cd scripts/; ./apply_flavor_izzy.sh)
|
||||||
flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_3.3.2.sksl.json
|
flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_3.3.4.sksl.json
|
||||||
cp build/app/outputs/apk/izzy/release/*.apk outputs
|
cp build/app/outputs/apk/izzy/release/*.apk outputs
|
||||||
rm $AVES_STORE_FILE
|
rm $AVES_STORE_FILE
|
||||||
env:
|
env:
|
||||||
|
|
26
CHANGELOG.md
|
@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <a id="unreleased"></a>[Unreleased]
|
||||||
|
|
||||||
|
## <a id="v1.7.1"></a>[v1.7.1] - 2022-10-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- mosaic layout
|
||||||
|
- reverse filters to filter out/in
|
||||||
|
- Collection: selection edit actions available as quick actions
|
||||||
|
- Albums: group by content type
|
||||||
|
- Info: improved display for XMP
|
||||||
|
- Stats: top albums
|
||||||
|
- Stats: open full top listings
|
||||||
|
- Video: option for muted auto play
|
||||||
|
- Slideshow / Screen saver: option for no transition
|
||||||
|
- Slideshow / Screen saver: animated zoom effect
|
||||||
|
- Widget: tap action setting
|
||||||
|
- Wallpaper: scroll effect option
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- upgraded Flutter to stable v3.3.4
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- restoring to missing Download subdir
|
||||||
|
- crash when cataloguing PNG with large chunks
|
||||||
|
|
||||||
## <a id="v1.7.0"></a>[v1.7.0] - 2022-09-19
|
## <a id="v1.7.0"></a>[v1.7.0] - 2022-09-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -155,22 +155,22 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
|
||||||
implementation 'androidx.core:core-ktx:1.9.0'
|
implementation 'androidx.core:core-ktx:1.9.0'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.3'
|
implementation 'androidx.exifinterface:exifinterface:1.3.4'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'com.caverock:androidsvg-aar:1.4'
|
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||||
implementation 'com.commonsware.cwac:document:0.4.1'
|
implementation 'com.commonsware.cwac:document:0.5.0'
|
||||||
implementation 'com.drewnoakes:metadata-extractor:2.18.0'
|
implementation 'com.drewnoakes:metadata-extractor:2.18.0'
|
||||||
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
|
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
|
||||||
implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a'
|
implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a'
|
||||||
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/pixymeta-android
|
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/pixymeta-android
|
||||||
implementation 'com.github.deckerst:pixymeta-android:706bd73d6e'
|
implementation 'com.github.deckerst:pixymeta-android:706bd73d6e'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.13.2'
|
implementation 'com.github.bumptech.glide:glide:4.14.2'
|
||||||
|
|
||||||
// huawei flavor only
|
// huawei flavor only
|
||||||
huaweiImplementation 'com.huawei.agconnect:agconnect-core:1.5.2.300'
|
huaweiImplementation 'com.huawei.agconnect:agconnect-core:1.7.2.300'
|
||||||
|
|
||||||
kapt 'androidx.annotation:annotation:1.4.0'
|
kapt 'androidx.annotation:annotation:1.5.0'
|
||||||
kapt 'com.github.bumptech.glide:compiler:4.13.0'
|
kapt 'com.github.bumptech.glide:compiler:4.14.2'
|
||||||
|
|
||||||
compileOnly rootProject.findProject(':streams_channel')
|
compileOnly rootProject.findProject(':streams_channel')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Android Studio Chipmunk (2021.2.1) recommends:
|
Gradle v7.4 / Android Gradle Plugin v7.3.0 recommend:
|
||||||
- removing "package" from AndroidManifest.xml
|
- removing "package" from AndroidManifest.xml
|
||||||
- adding it as "namespace" in app/build.gradle
|
- adding it as "namespace" in app/build.gradle
|
||||||
This change eventually prevents building the app with Flutter v3.0.2.
|
This change eventually prevents building the app with Flutter v3.3.3.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
|
|
@ -3,6 +3,7 @@ package deckers.thibault.aves
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -21,7 +22,7 @@ import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class AnalysisService : MethodChannel.MethodCallHandler, Service() {
|
class AnalysisService : Service() {
|
||||||
private var flutterEngine: FlutterEngine? = null
|
private var flutterEngine: FlutterEngine? = null
|
||||||
private var backgroundChannel: MethodChannel? = null
|
private var backgroundChannel: MethodChannel? = null
|
||||||
private var serviceLooper: Looper? = null
|
private var serviceLooper: Looper? = null
|
||||||
|
@ -30,35 +31,13 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
Log.i(LOG_TAG, "Create analysis service")
|
Log.i(LOG_TAG, "Create analysis service")
|
||||||
val context = this
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
FlutterUtils.initFlutterEngine(context, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
|
FlutterUtils.initFlutterEngine(this@AnalysisService, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
|
||||||
flutterEngine = it
|
flutterEngine = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val messenger = flutterEngine!!.dartExecutor
|
initChannels(this)
|
||||||
|
|
||||||
// channels for analysis
|
|
||||||
|
|
||||||
// dart -> platform -> dart
|
|
||||||
// - need Context
|
|
||||||
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
|
|
||||||
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(this))
|
|
||||||
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
|
|
||||||
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
|
|
||||||
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))
|
|
||||||
|
|
||||||
// result streaming: dart -> platform ->->-> dart
|
|
||||||
// - need Context
|
|
||||||
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) }
|
|
||||||
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(this, args) }
|
|
||||||
|
|
||||||
// channel for service management
|
|
||||||
backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL).apply {
|
|
||||||
setMethodCallHandler(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerThread("Analysis service handler", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
HandlerThread("Analysis service handler", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
||||||
start()
|
start()
|
||||||
|
@ -94,7 +73,36 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
private fun detachAndStop() {
|
||||||
|
analysisServiceBinder.detach()
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initChannels(context: Context) {
|
||||||
|
val messenger = flutterEngine!!.dartExecutor
|
||||||
|
|
||||||
|
// channels for analysis
|
||||||
|
|
||||||
|
// dart -> platform -> dart
|
||||||
|
// - need Context
|
||||||
|
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context))
|
||||||
|
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(context))
|
||||||
|
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context))
|
||||||
|
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(context))
|
||||||
|
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(context))
|
||||||
|
|
||||||
|
// result streaming: dart -> platform ->->-> dart
|
||||||
|
// - need Context
|
||||||
|
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(context, args) }
|
||||||
|
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(context, args) }
|
||||||
|
|
||||||
|
// channel for service management
|
||||||
|
backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL).apply {
|
||||||
|
setMethodCallHandler { call, result -> onMethodCall(call, result) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"initialized" -> {
|
"initialized" -> {
|
||||||
Log.d(LOG_TAG, "background channel is ready")
|
Log.d(LOG_TAG, "background channel is ready")
|
||||||
|
@ -119,11 +127,6 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun detachAndStop() {
|
|
||||||
analysisServiceBinder.detach()
|
|
||||||
stopSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildNotification(title: String? = null, message: String? = null): Notification {
|
private fun buildNotification(title: String? = null, message: String? = null): Notification {
|
||||||
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
|
|
@ -3,7 +3,6 @@ package deckers.thibault.aves
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class HomeWidgetSettingsActivity : MainActivity() {
|
class HomeWidgetSettingsActivity : MainActivity() {
|
||||||
|
@ -26,7 +25,7 @@ class HomeWidgetSettingsActivity : MainActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val messenger = flutterEngine!!.dartExecutor
|
val messenger = flutterEngine!!.dartExecutor
|
||||||
MethodChannel(messenger, CHANNEL).setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
|
MethodChannel(messenger, CHANNEL).setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"configure" -> {
|
"configure" -> {
|
||||||
result.success(null)
|
result.success(null)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetProvider
|
import android.appwidget.AppWidgetProvider
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -62,6 +64,18 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getDevicePixelRatio(): Float = Resources.getSystem().displayMetrics.density
|
||||||
|
|
||||||
|
private fun getWidgetSizePx(context: Context, widgetInfo: Bundle): Pair<Int, Int> {
|
||||||
|
val devicePixelRatio = getDevicePixelRatio()
|
||||||
|
val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||||
|
val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
|
||||||
|
val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
|
||||||
|
val widthPx = (widgetInfo.getInt(widthKey) * devicePixelRatio).roundToInt()
|
||||||
|
val heightPx = (widgetInfo.getInt(heightKey) * devicePixelRatio).roundToInt()
|
||||||
|
return Pair(widthPx, heightPx)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getBytes(
|
private suspend fun getBytes(
|
||||||
context: Context,
|
context: Context,
|
||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
|
@ -69,9 +83,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
drawEntryImage: Boolean,
|
drawEntryImage: Boolean,
|
||||||
reuseEntry: Boolean = false,
|
reuseEntry: Boolean = false,
|
||||||
): ByteArray? {
|
): ByteArray? {
|
||||||
val devicePixelRatio = context.resources.displayMetrics.density
|
val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
|
||||||
val widthPx = (widgetInfo.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) * devicePixelRatio).roundToInt()
|
|
||||||
val heightPx = (widgetInfo.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) * devicePixelRatio).roundToInt()
|
|
||||||
if (widthPx == 0 || heightPx == 0) return null
|
if (widthPx == 0 || heightPx == 0) return null
|
||||||
|
|
||||||
initFlutterEngine(context)
|
initFlutterEngine(context)
|
||||||
|
@ -85,7 +97,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
"widgetId" to widgetId,
|
"widgetId" to widgetId,
|
||||||
"widthPx" to widthPx,
|
"widthPx" to widthPx,
|
||||||
"heightPx" to heightPx,
|
"heightPx" to heightPx,
|
||||||
"devicePixelRatio" to devicePixelRatio,
|
"devicePixelRatio" to getDevicePixelRatio(),
|
||||||
"drawEntryImage" to drawEntryImage,
|
"drawEntryImage" to drawEntryImage,
|
||||||
"reuseEntry" to reuseEntry,
|
"reuseEntry" to reuseEntry,
|
||||||
), object : MethodChannel.Result {
|
), object : MethodChannel.Result {
|
||||||
|
@ -120,9 +132,8 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
) {
|
) {
|
||||||
bytes ?: return
|
bytes ?: return
|
||||||
|
|
||||||
val devicePixelRatio = context.resources.displayMetrics.density
|
val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
|
||||||
val widthPx = (widgetInfo.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) * devicePixelRatio).roundToInt()
|
if (widthPx == 0 || heightPx == 0) return
|
||||||
val heightPx = (widgetInfo.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) * devicePixelRatio).roundToInt()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
|
val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
|
||||||
|
@ -198,6 +209,5 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(context, args) }
|
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(context, args) }
|
||||||
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(context, args) }
|
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(context, args) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import deckers.thibault.aves.utils.ContextUtils.resourceUri
|
||||||
import deckers.thibault.aves.utils.FlutterUtils
|
import deckers.thibault.aves.utils.FlutterUtils
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -74,14 +73,15 @@ class SearchSuggestionsProvider : ContentProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val messenger = flutterEngine!!.dartExecutor
|
val messenger = flutterEngine!!.dartExecutor
|
||||||
val backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL)
|
val backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL).apply {
|
||||||
backgroundChannel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
|
setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"initialized" -> {
|
"initialized" -> {
|
||||||
Log.d(LOG_TAG, "background channel is ready")
|
Log.d(LOG_TAG, "background channel is ready")
|
||||||
result.success(null)
|
result.success(null)
|
||||||
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
else -> result.notImplemented()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package deckers.thibault.aves
|
package deckers.thibault.aves
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -16,50 +17,22 @@ import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
import deckers.thibault.aves.utils.getParcelableExtraCompat
|
import deckers.thibault.aves.utils.getParcelableExtraCompat
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class WallpaperActivity : FlutterActivity() {
|
class WallpaperActivity : FlutterActivity() {
|
||||||
private lateinit var intentDataMap: MutableMap<String, Any?>
|
private lateinit var intentDataMap: MutableMap<String, Any?>
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
Log.i(LOG_TAG, "onCreate intent=$intent")
|
Log.i(LOG_TAG, "onCreate intent=$intent")
|
||||||
intent.extras?.takeUnless { it.isEmpty }?.let {
|
intent.extras?.takeUnless { it.isEmpty }?.let {
|
||||||
Log.i(LOG_TAG, "onCreate intent extras=$it")
|
Log.i(LOG_TAG, "onCreate intent extras=$it")
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
val messenger = flutterEngine!!.dartExecutor
|
|
||||||
|
|
||||||
// dart -> platform -> dart
|
|
||||||
// - need Context
|
|
||||||
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
|
|
||||||
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
|
|
||||||
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(context))
|
|
||||||
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
|
|
||||||
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
|
|
||||||
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))
|
|
||||||
// - need ContextWrapper
|
|
||||||
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
|
|
||||||
MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
|
|
||||||
// - need Activity
|
|
||||||
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
|
|
||||||
|
|
||||||
// result streaming: dart -> platform ->->-> dart
|
|
||||||
// - need Context
|
|
||||||
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) }
|
|
||||||
|
|
||||||
// intent handling
|
|
||||||
// detail fetch: dart -> platform
|
|
||||||
intentDataMap = extractIntentData(intent)
|
intentDataMap = extractIntentData(intent)
|
||||||
MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result ->
|
|
||||||
when (call.method) {
|
initChannels(this)
|
||||||
"getIntentData" -> {
|
|
||||||
result.success(intentDataMap)
|
|
||||||
intentDataMap.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
@ -76,6 +49,41 @@ class WallpaperActivity : FlutterActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initChannels(activity: Activity) {
|
||||||
|
val messenger = flutterEngine!!.dartExecutor
|
||||||
|
|
||||||
|
// dart -> platform -> dart
|
||||||
|
// - need Context
|
||||||
|
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(activity))
|
||||||
|
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(activity))
|
||||||
|
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(activity))
|
||||||
|
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(activity))
|
||||||
|
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(activity))
|
||||||
|
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(activity))
|
||||||
|
// - need ContextWrapper
|
||||||
|
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(activity))
|
||||||
|
MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(activity))
|
||||||
|
// - need Activity
|
||||||
|
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(activity))
|
||||||
|
|
||||||
|
// result streaming: dart -> platform ->->-> dart
|
||||||
|
// - need Context
|
||||||
|
StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(activity, args) }
|
||||||
|
|
||||||
|
// intent handling
|
||||||
|
// detail fetch: dart -> platform
|
||||||
|
MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result -> onMethodCall(call, result) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
when (call.method) {
|
||||||
|
"getIntentData" -> {
|
||||||
|
result.success(intentDataMap)
|
||||||
|
intentDataMap.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
|
private fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
|
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
|
||||||
|
|
|
@ -21,7 +21,7 @@ class AvesAppGlideModule : AppGlideModule() {
|
||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||||
// prevent ExifInterface error logs
|
// prevent ExifInterface error logs
|
||||||
// cf https://github.com/bumptech/glide/issues/3383
|
// cf https://github.com/bumptech/glide/issues/3383
|
||||||
glide.registry.imageHeaderParsers.compatRemoveIf { parser: ImageHeaderParser? -> parser is ExifInterfaceImageHeaderParser }
|
registry.imageHeaderParsers.compatRemoveIf { parser: ImageHeaderParser? -> parser is ExifInterfaceImageHeaderParser }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isManifestParsingEnabled(): Boolean = false
|
override fun isManifestParsingEnabled(): Boolean = false
|
||||||
|
|
|
@ -98,7 +98,7 @@ object XMP {
|
||||||
if (MimeTypes.isHeic(mimeType) && !foundXmp && StorageUtils.isMediaStoreContentUri(uri) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (MimeTypes.isHeic(mimeType) && !foundXmp && StorageUtils.isMediaStoreContentUri(uri) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
try {
|
try {
|
||||||
val xmpBytes = context.queryContentResolverProp(uri, mimeType, MediaStore.MediaColumns.XMP)
|
val xmpBytes = context.queryContentResolverProp(uri, mimeType, MediaStore.MediaColumns.XMP)
|
||||||
if (xmpBytes is ByteArray) {
|
if (xmpBytes is ByteArray && xmpBytes.size > 0) {
|
||||||
val xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes, SafeXmpReader.PARSE_OPTIONS)
|
val xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes, SafeXmpReader.PARSE_OPTIONS)
|
||||||
processXmp(xmpMeta)
|
processXmp(xmpMeta)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ object Helper {
|
||||||
|
|
||||||
val metadata = when (fileType) {
|
val metadata = when (fileType) {
|
||||||
FileType.Jpeg -> safeReadJpeg(inputStream)
|
FileType.Jpeg -> safeReadJpeg(inputStream)
|
||||||
|
FileType.Png -> safeReadPng(inputStream)
|
||||||
FileType.Tiff,
|
FileType.Tiff,
|
||||||
FileType.Arw,
|
FileType.Arw,
|
||||||
FileType.Cr2,
|
FileType.Cr2,
|
||||||
|
@ -95,6 +96,10 @@ object Helper {
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun safeReadPng(input: InputStream): com.drew.metadata.Metadata {
|
||||||
|
return SafePngMetadataReader.readMetadata(input)
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, TiffProcessingException::class)
|
@Throws(IOException::class, TiffProcessingException::class)
|
||||||
fun safeReadTiff(input: InputStream): com.drew.metadata.Metadata {
|
fun safeReadTiff(input: InputStream): com.drew.metadata.Metadata {
|
||||||
val reader = RandomAccessStreamReader(input, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, safeReadStreamLength)
|
val reader = RandomAccessStreamReader(input, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, safeReadStreamLength)
|
||||||
|
|
|
@ -0,0 +1,302 @@
|
||||||
|
package deckers.thibault.aves.metadata.metadataextractor
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.drew.imaging.png.*
|
||||||
|
import com.drew.imaging.tiff.TiffProcessingException
|
||||||
|
import com.drew.imaging.tiff.TiffReader
|
||||||
|
import com.drew.lang.*
|
||||||
|
import com.drew.lang.annotations.NotNull
|
||||||
|
import com.drew.metadata.ErrorDirectory
|
||||||
|
import com.drew.metadata.Metadata
|
||||||
|
import com.drew.metadata.StringValue
|
||||||
|
import com.drew.metadata.exif.ExifTiffHandler
|
||||||
|
import com.drew.metadata.icc.IccReader
|
||||||
|
import com.drew.metadata.png.PngChromaticitiesDirectory
|
||||||
|
import com.drew.metadata.png.PngDirectory
|
||||||
|
import com.drew.metadata.xmp.XmpReader
|
||||||
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.zip.InflaterInputStream
|
||||||
|
import java.util.zip.ZipException
|
||||||
|
|
||||||
|
// adapted from `PngMetadataReader` to prevent reading OOM from large chunks
|
||||||
|
// as of `metadata-extractor` v2.18.0, there is no way to customize the reader
|
||||||
|
// without copying `desiredChunkTypes` and the whole `processChunk` function
|
||||||
|
object SafePngMetadataReader {
|
||||||
|
private val LOG_TAG = LogUtils.createTag<SafePngMetadataReader>()
|
||||||
|
|
||||||
|
// arbitrary size to detect chunks that may yield an OOM
|
||||||
|
private const val chunkSizeDangerThreshold = SafeXmpReader.segmentTypeSizeDangerThreshold
|
||||||
|
|
||||||
|
private val latin1Encoding = Charsets.ISO_8859_1
|
||||||
|
private val desiredChunkTypes: Set<PngChunkType> = hashSetOf(
|
||||||
|
PngChunkType.IHDR,
|
||||||
|
PngChunkType.PLTE,
|
||||||
|
PngChunkType.tRNS,
|
||||||
|
PngChunkType.cHRM,
|
||||||
|
PngChunkType.sRGB,
|
||||||
|
PngChunkType.gAMA,
|
||||||
|
PngChunkType.iCCP,
|
||||||
|
PngChunkType.bKGD,
|
||||||
|
PngChunkType.tEXt,
|
||||||
|
PngChunkType.zTXt,
|
||||||
|
PngChunkType.iTXt,
|
||||||
|
PngChunkType.tIME,
|
||||||
|
PngChunkType.pHYs,
|
||||||
|
PngChunkType.sBIT,
|
||||||
|
PngChunkType.eXIf,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Throws(IOException::class, PngProcessingException::class)
|
||||||
|
fun readMetadata(inputStream: InputStream): Metadata {
|
||||||
|
val chunks = PngChunkReader().extract(StreamReader(inputStream), desiredChunkTypes)
|
||||||
|
val metadata = Metadata()
|
||||||
|
for (chunk in chunks) {
|
||||||
|
try {
|
||||||
|
processChunk(metadata, chunk)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
metadata.addDirectory(ErrorDirectory("Exception reading PNG chunk: " + e.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(PngProcessingException::class, IOException::class)
|
||||||
|
private fun processChunk(@NotNull metadata: Metadata, @NotNull chunk: PngChunk) {
|
||||||
|
val chunkType = chunk.type
|
||||||
|
val bytes = chunk.bytes
|
||||||
|
|
||||||
|
// TLAD insert start
|
||||||
|
if (bytes.size > chunkSizeDangerThreshold) {
|
||||||
|
Log.w(LOG_TAG, "PNG chunk $chunkType is too large, with a size of ${bytes.size} B")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TLAD insert end
|
||||||
|
|
||||||
|
if (chunkType == PngChunkType.IHDR) {
|
||||||
|
val header = PngHeader(bytes)
|
||||||
|
val directory = PngDirectory(PngChunkType.IHDR)
|
||||||
|
directory.setInt(PngDirectory.TAG_IMAGE_WIDTH, header.imageWidth)
|
||||||
|
directory.setInt(PngDirectory.TAG_IMAGE_HEIGHT, header.imageHeight)
|
||||||
|
directory.setInt(PngDirectory.TAG_BITS_PER_SAMPLE, header.bitsPerSample.toInt())
|
||||||
|
directory.setInt(PngDirectory.TAG_COLOR_TYPE, header.colorType.numericValue)
|
||||||
|
directory.setInt(PngDirectory.TAG_COMPRESSION_TYPE, header.compressionType.toInt() and 0xFF) // make sure it's unsigned
|
||||||
|
directory.setInt(PngDirectory.TAG_FILTER_METHOD, header.filterMethod.toInt())
|
||||||
|
directory.setInt(PngDirectory.TAG_INTERLACE_METHOD, header.interlaceMethod.toInt())
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.PLTE) {
|
||||||
|
val directory = PngDirectory(PngChunkType.PLTE)
|
||||||
|
directory.setInt(PngDirectory.TAG_PALETTE_SIZE, bytes.size / 3)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.tRNS) {
|
||||||
|
val directory = PngDirectory(PngChunkType.tRNS)
|
||||||
|
directory.setInt(PngDirectory.TAG_PALETTE_HAS_TRANSPARENCY, 1)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.sRGB) {
|
||||||
|
val srgbRenderingIntent = bytes[0].toInt()
|
||||||
|
val directory = PngDirectory(PngChunkType.sRGB)
|
||||||
|
directory.setInt(PngDirectory.TAG_SRGB_RENDERING_INTENT, srgbRenderingIntent)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.cHRM) {
|
||||||
|
val chromaticities = PngChromaticities(bytes)
|
||||||
|
val directory = PngChromaticitiesDirectory()
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_WHITE_POINT_X, chromaticities.whitePointX)
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_WHITE_POINT_Y, chromaticities.whitePointY)
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_RED_X, chromaticities.redX)
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_RED_Y, chromaticities.redY)
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_GREEN_X, chromaticities.greenX)
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_GREEN_Y, chromaticities.greenY)
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_BLUE_X, chromaticities.blueX)
|
||||||
|
directory.setInt(PngChromaticitiesDirectory.TAG_BLUE_Y, chromaticities.blueY)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.gAMA) {
|
||||||
|
val gammaInt = ByteConvert.toInt32BigEndian(bytes)
|
||||||
|
SequentialByteArrayReader(bytes).int32
|
||||||
|
val directory = PngDirectory(PngChunkType.gAMA)
|
||||||
|
directory.setDouble(PngDirectory.TAG_GAMMA, gammaInt / 100000.0)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.iCCP) {
|
||||||
|
val reader: SequentialReader = SequentialByteArrayReader(bytes)
|
||||||
|
|
||||||
|
// Profile Name is 1-79 bytes, followed by the 1 byte null character
|
||||||
|
val profileNameBytes = reader.getNullTerminatedBytes(79 + 1)
|
||||||
|
val directory = PngDirectory(PngChunkType.iCCP)
|
||||||
|
directory.setStringValue(PngDirectory.TAG_ICC_PROFILE_NAME, StringValue(profileNameBytes, latin1Encoding))
|
||||||
|
val compressionMethod = reader.int8
|
||||||
|
// Only compression method allowed by the spec is zero: deflate
|
||||||
|
if (compressionMethod.toInt() == 0) {
|
||||||
|
// bytes left for compressed text is:
|
||||||
|
// total bytes length - (profilenamebytes length + null byte + compression method byte)
|
||||||
|
val bytesLeft = bytes.size - (profileNameBytes.size + 1 + 1)
|
||||||
|
val compressedProfile = reader.getBytes(bytesLeft)
|
||||||
|
try {
|
||||||
|
val inflateStream = InflaterInputStream(ByteArrayInputStream(compressedProfile))
|
||||||
|
IccReader().extract(RandomAccessStreamReader(inflateStream), metadata, directory)
|
||||||
|
inflateStream.close()
|
||||||
|
} catch (zex: ZipException) {
|
||||||
|
directory.addError(String.format("Exception decompressing PNG iCCP chunk : %s", zex.message))
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
directory.addError("Invalid compression method value")
|
||||||
|
}
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.bKGD) {
|
||||||
|
val directory = PngDirectory(PngChunkType.bKGD)
|
||||||
|
directory.setByteArray(PngDirectory.TAG_BACKGROUND_COLOR, bytes)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.tEXt) {
|
||||||
|
val reader: SequentialReader = SequentialByteArrayReader(bytes)
|
||||||
|
|
||||||
|
// Keyword is 1-79 bytes, followed by the 1 byte null character
|
||||||
|
val keywordsv = reader.getNullTerminatedStringValue(79 + 1, latin1Encoding)
|
||||||
|
val keyword = keywordsv.toString()
|
||||||
|
|
||||||
|
// bytes left for text is:
|
||||||
|
// total bytes length - (Keyword length + null byte)
|
||||||
|
val bytesLeft = bytes.size - (keywordsv.bytes.size + 1)
|
||||||
|
val value = reader.getNullTerminatedStringValue(bytesLeft, latin1Encoding)
|
||||||
|
val textPairs: MutableList<KeyValuePair> = ArrayList()
|
||||||
|
textPairs.add(KeyValuePair(keyword, value))
|
||||||
|
val directory = PngDirectory(PngChunkType.tEXt)
|
||||||
|
directory.setObject(PngDirectory.TAG_TEXTUAL_DATA, textPairs)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.zTXt) {
|
||||||
|
val reader: SequentialReader = SequentialByteArrayReader(bytes)
|
||||||
|
|
||||||
|
// Keyword is 1-79 bytes, followed by the 1 byte null character
|
||||||
|
val keywordsv = reader.getNullTerminatedStringValue(79 + 1, latin1Encoding)
|
||||||
|
val keyword = keywordsv.toString()
|
||||||
|
val compressionMethod = reader.int8
|
||||||
|
|
||||||
|
// bytes left for compressed text is:
|
||||||
|
// total bytes length - (Keyword length + null byte + compression method byte)
|
||||||
|
val bytesLeft = bytes.size - (keywordsv.bytes.size + 1 + 1)
|
||||||
|
var textBytes: ByteArray? = null
|
||||||
|
if (compressionMethod.toInt() == 0) {
|
||||||
|
try {
|
||||||
|
textBytes = StreamUtil.readAllBytes(InflaterInputStream(ByteArrayInputStream(bytes, bytes.size - bytesLeft, bytesLeft)))
|
||||||
|
} catch (zex: ZipException) {
|
||||||
|
val directory = PngDirectory(PngChunkType.zTXt)
|
||||||
|
directory.addError(String.format("Exception decompressing PNG zTXt chunk with keyword \"%s\": %s", keyword, zex.message))
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val directory = PngDirectory(PngChunkType.zTXt)
|
||||||
|
directory.addError("Invalid compression method value")
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
if (textBytes != null) {
|
||||||
|
if (keyword == "XML:com.adobe.xmp") {
|
||||||
|
// NOTE in testing images, the XMP has parsed successfully, but we are not extracting tags from it as necessary
|
||||||
|
XmpReader().extract(textBytes, metadata)
|
||||||
|
} else {
|
||||||
|
val textPairs: MutableList<KeyValuePair> = ArrayList()
|
||||||
|
textPairs.add(KeyValuePair(keyword, StringValue(textBytes, latin1Encoding)))
|
||||||
|
val directory = PngDirectory(PngChunkType.zTXt)
|
||||||
|
directory.setObject(PngDirectory.TAG_TEXTUAL_DATA, textPairs)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (chunkType == PngChunkType.iTXt) {
|
||||||
|
val reader: SequentialReader = SequentialByteArrayReader(bytes)
|
||||||
|
|
||||||
|
// Keyword is 1-79 bytes, followed by the 1 byte null character
|
||||||
|
val keywordsv = reader.getNullTerminatedStringValue(79 + 1, latin1Encoding)
|
||||||
|
val keyword = keywordsv.toString()
|
||||||
|
val compressionFlag = reader.int8
|
||||||
|
val compressionMethod = reader.int8
|
||||||
|
// TODO we currently ignore languageTagBytes and translatedKeywordBytes
|
||||||
|
val languageTagBytes = reader.getNullTerminatedBytes(bytes.size)
|
||||||
|
val translatedKeywordBytes = reader.getNullTerminatedBytes(bytes.size)
|
||||||
|
|
||||||
|
// bytes left for compressed text is:
|
||||||
|
// total bytes length - (Keyword length + null byte + comp flag byte + comp method byte + lang length + null byte + translated length + null byte)
|
||||||
|
val bytesLeft = bytes.size - (keywordsv.bytes.size + 1 + 1 + 1 + languageTagBytes.size + 1 + translatedKeywordBytes.size + 1)
|
||||||
|
var textBytes: ByteArray? = null
|
||||||
|
if (compressionFlag.toInt() == 0) {
|
||||||
|
textBytes = reader.getNullTerminatedBytes(bytesLeft)
|
||||||
|
} else if (compressionFlag.toInt() == 1) {
|
||||||
|
if (compressionMethod.toInt() == 0) {
|
||||||
|
try {
|
||||||
|
textBytes = StreamUtil.readAllBytes(InflaterInputStream(ByteArrayInputStream(bytes, bytes.size - bytesLeft, bytesLeft)))
|
||||||
|
} catch (zex: ZipException) {
|
||||||
|
val directory = PngDirectory(PngChunkType.iTXt)
|
||||||
|
directory.addError(String.format("Exception decompressing PNG iTXt chunk with keyword \"%s\": %s", keyword, zex.message))
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val directory = PngDirectory(PngChunkType.iTXt)
|
||||||
|
directory.addError("Invalid compression method value")
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val directory = PngDirectory(PngChunkType.iTXt)
|
||||||
|
directory.addError("Invalid compression flag value")
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
if (textBytes != null) {
|
||||||
|
if (keyword == "XML:com.adobe.xmp") {
|
||||||
|
// NOTE in testing images, the XMP has parsed successfully, but we are not extracting tags from it as necessary
|
||||||
|
XmpReader().extract(textBytes, metadata)
|
||||||
|
} else {
|
||||||
|
val textPairs: MutableList<KeyValuePair> = ArrayList()
|
||||||
|
textPairs.add(KeyValuePair(keyword, StringValue(textBytes, latin1Encoding)))
|
||||||
|
val directory = PngDirectory(PngChunkType.iTXt)
|
||||||
|
directory.setObject(PngDirectory.TAG_TEXTUAL_DATA, textPairs)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (chunkType == PngChunkType.tIME) {
|
||||||
|
val reader = SequentialByteArrayReader(bytes)
|
||||||
|
val year = reader.uInt16
|
||||||
|
val month = reader.uInt8.toInt()
|
||||||
|
val day = reader.uInt8.toInt()
|
||||||
|
val hour = reader.uInt8.toInt()
|
||||||
|
val minute = reader.uInt8.toInt()
|
||||||
|
val second = reader.uInt8.toInt()
|
||||||
|
val directory = PngDirectory(PngChunkType.tIME)
|
||||||
|
if (DateUtil.isValidDate(year, month - 1, day) && DateUtil.isValidTime(hour, minute, second)) {
|
||||||
|
val dateString = String.format("%04d:%02d:%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
|
||||||
|
directory.setString(PngDirectory.TAG_LAST_MODIFICATION_TIME, dateString)
|
||||||
|
} else {
|
||||||
|
directory.addError(
|
||||||
|
String.format(
|
||||||
|
"PNG tIME data describes an invalid date/time: year=%d month=%d day=%d hour=%d minute=%d second=%d",
|
||||||
|
year, month, day, hour, minute, second
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.pHYs) {
|
||||||
|
val reader = SequentialByteArrayReader(bytes)
|
||||||
|
val pixelsPerUnitX = reader.int32
|
||||||
|
val pixelsPerUnitY = reader.int32
|
||||||
|
val unitSpecifier = reader.int8
|
||||||
|
val directory = PngDirectory(PngChunkType.pHYs)
|
||||||
|
directory.setInt(PngDirectory.TAG_PIXELS_PER_UNIT_X, pixelsPerUnitX)
|
||||||
|
directory.setInt(PngDirectory.TAG_PIXELS_PER_UNIT_Y, pixelsPerUnitY)
|
||||||
|
directory.setInt(PngDirectory.TAG_UNIT_SPECIFIER, unitSpecifier.toInt())
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.sBIT) {
|
||||||
|
val directory = PngDirectory(PngChunkType.sBIT)
|
||||||
|
directory.setByteArray(PngDirectory.TAG_SIGNIFICANT_BITS, bytes)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} else if (chunkType == PngChunkType.eXIf) {
|
||||||
|
try {
|
||||||
|
val handler = ExifTiffHandler(metadata, null)
|
||||||
|
TiffReader().processTiff(ByteArrayReader(bytes), handler, 0)
|
||||||
|
} catch (ex: TiffProcessingException) {
|
||||||
|
val directory = PngDirectory(PngChunkType.eXIf)
|
||||||
|
directory.addError(ex.message)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
val directory = PngDirectory(PngChunkType.eXIf)
|
||||||
|
directory.addError(ex.message)
|
||||||
|
metadata.addDirectory(directory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,7 +135,7 @@ class SafeXmpReader : XmpReader() {
|
||||||
private val LOG_TAG = LogUtils.createTag<SafeXmpReader>()
|
private val LOG_TAG = LogUtils.createTag<SafeXmpReader>()
|
||||||
|
|
||||||
// arbitrary size to detect extended XMP that may yield an OOM
|
// arbitrary size to detect extended XMP that may yield an OOM
|
||||||
private const val segmentTypeSizeDangerThreshold = 3 * (1 shl 20) // MB
|
const val segmentTypeSizeDangerThreshold = 3 * (1 shl 20) // MB
|
||||||
|
|
||||||
// tighter node limits for faster loading
|
// tighter node limits for faster loading
|
||||||
val PARSE_OPTIONS: ParseOptions = ParseOptions().setXMPNodesToLimit(
|
val PARSE_OPTIONS: ParseOptions = ParseOptions().setXMPNodesToLimit(
|
||||||
|
|
|
@ -41,6 +41,7 @@ class SourceEntry {
|
||||||
var height: Int? = null
|
var height: Int? = null
|
||||||
private var sourceRotationDegrees: Int? = null
|
private var sourceRotationDegrees: Int? = null
|
||||||
private var sizeBytes: Long? = null
|
private var sizeBytes: Long? = null
|
||||||
|
private var dateAddedSecs: Long? = null
|
||||||
private var dateModifiedSecs: Long? = null
|
private var dateModifiedSecs: Long? = null
|
||||||
private var sourceDateTakenMillis: Long? = null
|
private var sourceDateTakenMillis: Long? = null
|
||||||
private var durationMillis: Long? = null
|
private var durationMillis: Long? = null
|
||||||
|
@ -61,6 +62,7 @@ class SourceEntry {
|
||||||
sourceRotationDegrees = map["sourceRotationDegrees"] as Int?
|
sourceRotationDegrees = map["sourceRotationDegrees"] as Int?
|
||||||
sizeBytes = toLong(map["sizeBytes"])
|
sizeBytes = toLong(map["sizeBytes"])
|
||||||
title = map["title"] as String?
|
title = map["title"] as String?
|
||||||
|
dateAddedSecs = toLong(map["dateAddedSecs"])
|
||||||
dateModifiedSecs = toLong(map["dateModifiedSecs"])
|
dateModifiedSecs = toLong(map["dateModifiedSecs"])
|
||||||
sourceDateTakenMillis = toLong(map["sourceDateTakenMillis"])
|
sourceDateTakenMillis = toLong(map["sourceDateTakenMillis"])
|
||||||
durationMillis = toLong(map["durationMillis"])
|
durationMillis = toLong(map["durationMillis"])
|
||||||
|
@ -83,6 +85,7 @@ class SourceEntry {
|
||||||
"sourceRotationDegrees" to (sourceRotationDegrees ?: 0),
|
"sourceRotationDegrees" to (sourceRotationDegrees ?: 0),
|
||||||
"sizeBytes" to sizeBytes,
|
"sizeBytes" to sizeBytes,
|
||||||
"title" to title,
|
"title" to title,
|
||||||
|
"dateAddedSecs" to dateAddedSecs,
|
||||||
"dateModifiedSecs" to dateModifiedSecs,
|
"dateModifiedSecs" to dateModifiedSecs,
|
||||||
"sourceDateTakenMillis" to sourceDateTakenMillis,
|
"sourceDateTakenMillis" to sourceDateTakenMillis,
|
||||||
"durationMillis" to durationMillis,
|
"durationMillis" to durationMillis,
|
||||||
|
|
|
@ -22,7 +22,12 @@ internal class FileImageProvider : ImageProvider() {
|
||||||
try {
|
try {
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
entry.initFromFile(path, file.name, file.length(), file.lastModified() / 1000)
|
entry.initFromFile(
|
||||||
|
path = path,
|
||||||
|
title = file.name,
|
||||||
|
sizeBytes = file.length(),
|
||||||
|
dateModifiedSecs = file.lastModified() / 1000,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
callback.onFailure(e)
|
callback.onFailure(e)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import android.content.*
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.commonsware.cwac.document.DocumentFileCompat
|
import com.commonsware.cwac.document.DocumentFileCompat
|
||||||
|
@ -87,7 +86,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
val alwaysValid: NewEntryChecker = fun(_: Int, _: Int): Boolean = true
|
val alwaysValid: NewEntryChecker = fun(_: Int, _: Int): Boolean = true
|
||||||
val onSuccess: NewEntryHandler = fun(entry: FieldMap) { fetched.add(entry) }
|
val onSuccess: NewEntryHandler = fun(entry: FieldMap) { fetched.add(entry) }
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
if (!found && (sourceMimeType == null || isImage(sourceMimeType))) {
|
if (sourceMimeType == null || isImage(sourceMimeType)) {
|
||||||
val contentUri = ContentUris.withAppendedId(IMAGE_CONTENT_URI, id)
|
val contentUri = ContentUris.withAppendedId(IMAGE_CONTENT_URI, id)
|
||||||
found = fetchFrom(context, alwaysValid, onSuccess, contentUri, IMAGE_PROJECTION)
|
found = fetchFrom(context, alwaysValid, onSuccess, contentUri, IMAGE_PROJECTION)
|
||||||
}
|
}
|
||||||
|
@ -190,6 +189,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
|
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
|
||||||
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
|
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
|
||||||
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)
|
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)
|
||||||
|
val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
|
||||||
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
||||||
val dateTakenColumn = cursor.getColumnIndex(MediaColumns.DATE_TAKEN)
|
val dateTakenColumn = cursor.getColumnIndex(MediaColumns.DATE_TAKEN)
|
||||||
|
|
||||||
|
@ -225,6 +225,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
"height" to height,
|
"height" to height,
|
||||||
"sourceRotationDegrees" to if (orientationColumn != -1) cursor.getInt(orientationColumn) else 0,
|
"sourceRotationDegrees" to if (orientationColumn != -1) cursor.getInt(orientationColumn) else 0,
|
||||||
"sizeBytes" to cursor.getLong(sizeColumn),
|
"sizeBytes" to cursor.getLong(sizeColumn),
|
||||||
|
"dateAddedSecs" to cursor.getInt(dateAddedColumn),
|
||||||
"dateModifiedSecs" to dateModifiedSecs,
|
"dateModifiedSecs" to dateModifiedSecs,
|
||||||
"sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
|
"sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
|
||||||
"durationMillis" to durationMillis,
|
"durationMillis" to durationMillis,
|
||||||
|
@ -391,8 +392,13 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
effectiveTargetDir = targetDir
|
effectiveTargetDir = targetDir
|
||||||
targetDirDocFile = StorageUtils.createDirectoryDocIfAbsent(activity, targetDir)
|
targetDirDocFile = StorageUtils.createDirectoryDocIfAbsent(activity, targetDir)
|
||||||
if (!File(targetDir).exists()) {
|
if (!File(targetDir).exists()) {
|
||||||
callback.onFailure(Exception("failed to create directory at path=$targetDir"))
|
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
|
||||||
return
|
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
|
||||||
|
// download subdirectories can be created later by Media Store insertion
|
||||||
|
if (!isDownloadSubdir) {
|
||||||
|
callback.onFailure(Exception("failed to create directory at path=$targetDir"))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,54 +541,57 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
targetNameWithoutExtension: String,
|
targetNameWithoutExtension: String,
|
||||||
write: (OutputStream) -> Unit,
|
write: (OutputStream) -> Unit,
|
||||||
): String {
|
): String {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && isDownloadDir(activity, targetDir)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
|
||||||
val values = ContentValues().apply {
|
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
|
if (isDownloadSubdir) {
|
||||||
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
val volumePath = StorageUtils.getVolumePath(activity, targetDir)
|
||||||
}
|
val relativePath = targetDir.substring(volumePath?.length ?: 0)
|
||||||
val resolver = activity.contentResolver
|
|
||||||
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
|
||||||
|
|
||||||
uri?.let {
|
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
||||||
resolver.openOutputStream(uri)?.use(write)
|
val values = ContentValues().apply {
|
||||||
values.clear()
|
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
|
||||||
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
||||||
resolver.update(uri, values, null, null)
|
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||||
} ?: throw Exception("MediaStore failed for some reason")
|
|
||||||
|
|
||||||
File(targetDir, targetFileName).path
|
|
||||||
} else {
|
|
||||||
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
|
|
||||||
|
|
||||||
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
|
|
||||||
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
|
|
||||||
// through a document URI, not a tree URI
|
|
||||||
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
|
|
||||||
val targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
|
|
||||||
val targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
|
|
||||||
|
|
||||||
try {
|
|
||||||
targetDocFile.openOutputStream().use(write)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// remove empty file
|
|
||||||
if (targetDocFile.exists()) {
|
|
||||||
targetDocFile.delete()
|
|
||||||
}
|
}
|
||||||
throw e
|
val resolver = activity.contentResolver
|
||||||
|
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
||||||
|
|
||||||
|
uri?.let {
|
||||||
|
resolver.openOutputStream(uri)?.use(write)
|
||||||
|
values.clear()
|
||||||
|
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
||||||
|
resolver.update(uri, values, null, null)
|
||||||
|
} ?: throw Exception("MediaStore failed for some reason")
|
||||||
|
|
||||||
|
return File(targetDir, targetFileName).path
|
||||||
}
|
}
|
||||||
|
|
||||||
// the source file name and the created document file name can be different when:
|
|
||||||
// - a file with the same name already exists, some implementations give a suffix like ` (1)`, some *do not*
|
|
||||||
// - the original extension does not match the extension added by the underlying provider
|
|
||||||
val fileName = targetDocFile.name
|
|
||||||
targetDir + fileName
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun isDownloadDir(context: Context, dirPath: String): Boolean {
|
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
|
||||||
val relativeDir = removeTrailingSeparator(PathSegments(context, dirPath).relativeDir ?: "")
|
|
||||||
return relativeDir == Environment.DIRECTORY_DOWNLOADS
|
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
|
||||||
|
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
|
||||||
|
// through a document URI, not a tree URI
|
||||||
|
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
|
||||||
|
val targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
|
||||||
|
val targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
|
||||||
|
|
||||||
|
try {
|
||||||
|
targetDocFile.openOutputStream().use(write)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// remove empty file
|
||||||
|
if (targetDocFile.exists()) {
|
||||||
|
targetDocFile.delete()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
// the source file name and the created document file name can be different when:
|
||||||
|
// - a file with the same name already exists, some implementations give a suffix like ` (1)`, some *do not*
|
||||||
|
// - the original extension does not match the extension added by the underlying provider
|
||||||
|
val fileName = targetDocFile.name
|
||||||
|
return targetDir + fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun renameMultiple(
|
override suspend fun renameMultiple(
|
||||||
|
@ -782,6 +791,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
|
|
||||||
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
|
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
|
||||||
val projection = arrayOf(
|
val projection = arrayOf(
|
||||||
|
MediaStore.MediaColumns.DATE_ADDED,
|
||||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
|
@ -791,6 +801,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
newFields["uri"] = uri.toString()
|
newFields["uri"] = uri.toString()
|
||||||
newFields["contentId"] = uri.tryParseId()
|
newFields["contentId"] = uri.tryParseId()
|
||||||
newFields["path"] = path
|
newFields["path"] = path
|
||||||
|
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED).let { if (it != -1) newFields["dateAddedSecs"] = cursor.getInt(it) }
|
||||||
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
|
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return newFields
|
return newFields
|
||||||
|
@ -864,6 +875,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
MediaStore.MediaColumns.SIZE,
|
MediaStore.MediaColumns.SIZE,
|
||||||
MediaStore.MediaColumns.WIDTH,
|
MediaStore.MediaColumns.WIDTH,
|
||||||
MediaStore.MediaColumns.HEIGHT,
|
MediaStore.MediaColumns.HEIGHT,
|
||||||
|
MediaStore.MediaColumns.DATE_ADDED,
|
||||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||||
MediaColumns.DATE_TAKEN,
|
MediaColumns.DATE_TAKEN,
|
||||||
)
|
)
|
||||||
|
|
|
@ -104,9 +104,8 @@ object MimeTypes {
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
// as of androidx.exifinterface:exifinterface:1.3.3
|
// as of androidx.exifinterface:exifinterface:1.3.4
|
||||||
fun canEditExif(mimeType: String) = when (mimeType) {
|
fun canEditExif(mimeType: String) = when (mimeType) {
|
||||||
DNG,
|
|
||||||
JPEG,
|
JPEG,
|
||||||
PNG,
|
PNG,
|
||||||
WEBP -> true
|
WEBP -> true
|
||||||
|
|
|
@ -105,7 +105,11 @@ object PermissionManager {
|
||||||
val primaryDir = dirSegments.firstOrNull()
|
val primaryDir = dirSegments.firstOrNull()
|
||||||
if (getRestrictedPrimaryDirectories().contains(primaryDir) && dirSegments.size > 1) {
|
if (getRestrictedPrimaryDirectories().contains(primaryDir) && dirSegments.size > 1) {
|
||||||
// request secondary directory (if any) for restricted primary directory
|
// request secondary directory (if any) for restricted primary directory
|
||||||
dirSet.add(dirSegments.take(2).joinToString(File.separator))
|
val dir = dirSegments.take(2).joinToString(File.separator)
|
||||||
|
// only register directories that exist on storage, so they can be selected for access grant
|
||||||
|
if (File(volumePath, dir).exists()) {
|
||||||
|
dirSet.add(dir)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
primaryDir?.let { dirSet.add(it) }
|
primaryDir?.let { dirSet.add(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.content.pm.PackageManager
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import android.os.storage.StorageManager
|
import android.os.storage.StorageManager
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
@ -93,6 +94,10 @@ object StorageUtils {
|
||||||
return getVolumePaths(context).firstOrNull { anyPath.startsWith(it) }
|
return getVolumePaths(context).firstOrNull { anyPath.startsWith(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDownloadDirPath(context: Context, anyPath: String): String? {
|
||||||
|
return getVolumePath(context, anyPath)?.let { volumePath -> ensureTrailingSeparator(File(volumePath, Environment.DIRECTORY_DOWNLOADS).path) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun getPathStepIterator(context: Context, anyPath: String, root: String?): Iterator<String?>? {
|
private fun getPathStepIterator(context: Context, anyPath: String, root: String?): Iterator<String?>? {
|
||||||
val rootLength = (root ?: getVolumePath(context, anyPath))?.length ?: return null
|
val rootLength = (root ?: getVolumePath(context, anyPath))?.length ?: return null
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,13 @@ buildscript {
|
||||||
maven { url 'https://developer.huawei.com/repo/' }
|
maven { url 'https://developer.huawei.com/repo/' }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
// GMS & Firebase Crashlytics (used by some flavors only)
|
// GMS & Firebase Crashlytics (used by some flavors only)
|
||||||
classpath 'com.google.gms:google-services:4.3.14'
|
classpath 'com.google.gms:google-services:4.3.14'
|
||||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||||
// HMS (used by some flavors only)
|
// HMS (used by some flavors only)
|
||||||
classpath 'com.huawei.agconnect:agcp:1.5.2.300'
|
classpath 'com.huawei.agconnect:agcp:1.7.2.300'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {url 'https://developer.huawei.com/repo/'}
|
maven { url 'https://developer.huawei.com/repo/' }
|
||||||
}
|
}
|
||||||
// gradle.projectsEvaluated {
|
// gradle.projectsEvaluated {
|
||||||
// tasks.withType(JavaCompile) {
|
// tasks.withType(JavaCompile) {
|
||||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||||
|
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 283 KiB |
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 285 KiB |
5
fastlane/metadata/android/en-US/changelogs/1081.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
In v1.7.1:
|
||||||
|
- view your photos with the mosaic layout
|
||||||
|
- reverse filters to filter out/in
|
||||||
|
- set wallpapers with scroll effect
|
||||||
|
Full changelog available on GitHub
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 283 KiB |
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 285 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 283 KiB |
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 282 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 283 KiB |
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 284 KiB |
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "Anzeigen in Alben",
|
"chipActionGoToAlbumPage": "Anzeigen in Alben",
|
||||||
"chipActionGoToCountryPage": "Anzeigen in Ländern",
|
"chipActionGoToCountryPage": "Anzeigen in Ländern",
|
||||||
"chipActionGoToTagPage": "Zeige in Tags",
|
"chipActionGoToTagPage": "Zeige in Tags",
|
||||||
|
"chipActionFilterOut": "Filtern ohne",
|
||||||
|
"chipActionFilterIn": "Filtern mit",
|
||||||
"chipActionHide": "Ausblenden",
|
"chipActionHide": "Ausblenden",
|
||||||
"chipActionPin": "Oben Anpinnen",
|
"chipActionPin": "Oben Anpinnen",
|
||||||
"chipActionUnpin": "Nicht mehr Anpinen",
|
"chipActionUnpin": "Nicht mehr Anpinen",
|
||||||
|
@ -88,15 +90,18 @@
|
||||||
|
|
||||||
"entryInfoActionEditDate": "Datum & Uhrzeit bearbeiten",
|
"entryInfoActionEditDate": "Datum & Uhrzeit bearbeiten",
|
||||||
"entryInfoActionEditLocation": "Standort bearbeiten",
|
"entryInfoActionEditLocation": "Standort bearbeiten",
|
||||||
|
"entryInfoActionEditTitleDescription": "Titel und Beschreibung bearbeiten",
|
||||||
"entryInfoActionEditRating": "Bewertung bearbeiten",
|
"entryInfoActionEditRating": "Bewertung bearbeiten",
|
||||||
"entryInfoActionEditTags": "Tags bearbeiten",
|
"entryInfoActionEditTags": "Tags bearbeiten",
|
||||||
"entryInfoActionRemoveMetadata": "Metadaten entfernen",
|
"entryInfoActionRemoveMetadata": "Metadaten entfernen",
|
||||||
|
|
||||||
"filterBinLabel": "Papierkorb",
|
"filterBinLabel": "Papierkorb",
|
||||||
"filterFavouriteLabel": "Favorit",
|
"filterFavouriteLabel": "Favorit",
|
||||||
|
"filterNoDateLabel": "Undatiert",
|
||||||
"filterNoLocationLabel": "Ungeortet",
|
"filterNoLocationLabel": "Ungeortet",
|
||||||
"filterNoRatingLabel": "Nicht bewertet",
|
"filterNoRatingLabel": "Nicht bewertet",
|
||||||
"filterNoTagLabel": "Unmarkiert",
|
"filterNoTagLabel": "Unmarkiert",
|
||||||
|
"filterNoTitleLabel": "Unbenannt",
|
||||||
"filterOnThisDayLabel": "Am heutigen Tag",
|
"filterOnThisDayLabel": "Am heutigen Tag",
|
||||||
"filterRecentlyAddedLabel": "Kürzlich hinzugefügt",
|
"filterRecentlyAddedLabel": "Kürzlich hinzugefügt",
|
||||||
"filterRatingRejectedLabel": "Verworfen",
|
"filterRatingRejectedLabel": "Verworfen",
|
||||||
|
@ -152,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Höchste Rate",
|
"displayRefreshRatePreferHighest": "Höchste Rate",
|
||||||
"displayRefreshRatePreferLowest": "Niedrigste Rate",
|
"displayRefreshRatePreferLowest": "Niedrigste Rate",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Überspringen",
|
"videoPlaybackSkip": "Überspringen",
|
||||||
"slideshowVideoPlaybackMuted": "Stumm abspielen",
|
"videoPlaybackMuted": "Stumm abspielen",
|
||||||
"slideshowVideoPlaybackWithSound": "Mit Ton abspielen",
|
"videoPlaybackWithSound": "Mit Ton abspielen",
|
||||||
|
|
||||||
"themeBrightnessLight": "Hell",
|
"themeBrightnessLight": "Hell",
|
||||||
"themeBrightnessDark": "Dunkel",
|
"themeBrightnessDark": "Dunkel",
|
||||||
|
@ -164,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "Parallaxe",
|
"viewerTransitionParallax": "Parallaxe",
|
||||||
"viewerTransitionFade": "Ausblenden",
|
"viewerTransitionFade": "Ausblenden",
|
||||||
"viewerTransitionZoomIn": "Heranzoomen",
|
"viewerTransitionZoomIn": "Heranzoomen",
|
||||||
|
"viewerTransitionNone": "Keine",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Startbildschirm",
|
"wallpaperTargetHome": "Startbildschirm",
|
||||||
"wallpaperTargetLock": "Sperrbildschirm",
|
"wallpaperTargetLock": "Sperrbildschirm",
|
||||||
"wallpaperTargetHomeLock": "Start- und Sperrbildschirm",
|
"wallpaperTargetHomeLock": "Start- und Sperrbildschirm",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Startseite öffnen",
|
||||||
|
"widgetOpenPageViewer": "Viewer öffnen",
|
||||||
|
|
||||||
"albumTierNew": "Neu",
|
"albumTierNew": "Neu",
|
||||||
"albumTierPinned": "Angeheftet",
|
"albumTierPinned": "Angeheftet",
|
||||||
"albumTierSpecial": "Häufig verwendet",
|
"albumTierSpecial": "Häufig verwendet",
|
||||||
|
@ -284,7 +293,9 @@
|
||||||
"viewDialogSortSectionTitle": "Sortieren",
|
"viewDialogSortSectionTitle": "Sortieren",
|
||||||
"viewDialogGroupSectionTitle": "Gruppe",
|
"viewDialogGroupSectionTitle": "Gruppe",
|
||||||
"viewDialogLayoutSectionTitle": "Layout",
|
"viewDialogLayoutSectionTitle": "Layout",
|
||||||
|
"viewDialogReverseSortOrder": "Umgekehrte Sortierung",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Mosaik",
|
||||||
"tileLayoutGrid": "Kacheln",
|
"tileLayoutGrid": "Kacheln",
|
||||||
"tileLayoutList": "Liste",
|
"tileLayoutList": "Liste",
|
||||||
|
|
||||||
|
@ -387,10 +398,22 @@
|
||||||
"sortByAlbumFileName": "Nach Album & Dateiname",
|
"sortByAlbumFileName": "Nach Album & Dateiname",
|
||||||
"sortByRating": "Nach Bewertung",
|
"sortByRating": "Nach Bewertung",
|
||||||
|
|
||||||
|
"sortOrderNewestFirst": "Neueste zuerst",
|
||||||
|
"sortOrderOldestFirst": "Älteste zuerst",
|
||||||
|
"sortOrderAtoZ": "A zu Z",
|
||||||
|
"sortOrderZtoA": "Z zu A",
|
||||||
|
"sortOrderHighestFirst": "Höchste zuerst",
|
||||||
|
"sortOrderLowestFirst": "Niedrigste zuerst",
|
||||||
|
"sortOrderLargestFirst": "Größtes zuerst",
|
||||||
|
"sortOrderSmallestFirst": "Kleinste zuerst",
|
||||||
|
|
||||||
"albumGroupTier": "Nach Ebene",
|
"albumGroupTier": "Nach Ebene",
|
||||||
|
"albumGroupType": "Nach Typ",
|
||||||
"albumGroupVolume": "Nach Speichervolumen",
|
"albumGroupVolume": "Nach Speichervolumen",
|
||||||
"albumGroupNone": "Nicht gruppieren",
|
"albumGroupNone": "Nicht gruppieren",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Gemischt",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "In Album kopieren",
|
"albumPickPageTitleCopy": "In Album kopieren",
|
||||||
"albumPickPageTitleExport": "In Album exportieren",
|
"albumPickPageTitleExport": "In Album exportieren",
|
||||||
"albumPickPageTitleMove": "Zum Album verschieben",
|
"albumPickPageTitleMove": "Zum Album verschieben",
|
||||||
|
@ -424,10 +447,12 @@
|
||||||
"searchPlacesSectionTitle": "Orte",
|
"searchPlacesSectionTitle": "Orte",
|
||||||
"searchTagsSectionTitle": "Tags",
|
"searchTagsSectionTitle": "Tags",
|
||||||
"searchRatingSectionTitle": "Bewertungen",
|
"searchRatingSectionTitle": "Bewertungen",
|
||||||
|
"searchMetadataSectionTitle": "Metadaten",
|
||||||
|
|
||||||
"settingsPageTitle": "Einstellungen",
|
"settingsPageTitle": "Einstellungen",
|
||||||
"settingsSystemDefault": "System",
|
"settingsSystemDefault": "System",
|
||||||
"settingsDefault": "Standard",
|
"settingsDefault": "Standard",
|
||||||
|
"settingsDisabled": "Deaktiviert",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Einstellungen durchsuchen",
|
"settingsSearchFieldLabel": "Einstellungen durchsuchen",
|
||||||
"settingsSearchEmpty": "Keine passende Einstellung",
|
"settingsSearchEmpty": "Keine passende Einstellung",
|
||||||
|
@ -510,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "Wiederholung",
|
"settingsSlideshowRepeat": "Wiederholung",
|
||||||
"settingsSlideshowShuffle": "Mischen",
|
"settingsSlideshowShuffle": "Mischen",
|
||||||
"settingsSlideshowFillScreen": "Bildschirm ausfüllen",
|
"settingsSlideshowFillScreen": "Bildschirm ausfüllen",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Animierter Zoomeffekt",
|
||||||
"settingsSlideshowTransitionTile": "Übergang",
|
"settingsSlideshowTransitionTile": "Übergang",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Übergang",
|
|
||||||
"settingsSlideshowIntervalTile": "Intervall",
|
"settingsSlideshowIntervalTile": "Intervall",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Intervall",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Videowiedergabe",
|
"settingsSlideshowVideoPlaybackTile": "Videowiedergabe",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Videowiedergabe",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Videowiedergabe",
|
||||||
|
|
||||||
|
@ -521,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "Video",
|
"settingsVideoSectionTitle": "Video",
|
||||||
"settingsVideoShowVideos": "Videos anzeigen",
|
"settingsVideoShowVideos": "Videos anzeigen",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Hardware-Beschleunigung",
|
"settingsVideoEnableHardwareAcceleration": "Hardware-Beschleunigung",
|
||||||
"settingsVideoEnableAutoPlay": "Automatische Wiedergabe",
|
"settingsVideoAutoPlay": "Automatische Wiedergabe",
|
||||||
"settingsVideoLoopModeTile": "Schleifen-Modus",
|
"settingsVideoLoopModeTile": "Schleifen-Modus",
|
||||||
"settingsVideoLoopModeDialogTitle": "Schleifen-Modus",
|
"settingsVideoLoopModeDialogTitle": "Schleifen-Modus",
|
||||||
|
|
||||||
|
@ -543,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "Steuerung",
|
"settingsVideoControlsTile": "Steuerung",
|
||||||
"settingsVideoControlsPageTitle": "Steuerung",
|
"settingsVideoControlsPageTitle": "Steuerung",
|
||||||
"settingsVideoButtonsTile": "Schaltflächen",
|
"settingsVideoButtonsTile": "Schaltflächen",
|
||||||
"settingsVideoButtonsDialogTitle": "Schaltflächen",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Doppeltippen zum Abspielen/Pausieren",
|
"settingsVideoGestureDoubleTapTogglePlay": "Doppeltippen zum Abspielen/Pausieren",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Doppeltippen auf die Bildschirmränder zum Rückwärts-/Vorwärtsspringen",
|
"settingsVideoGestureSideDoubleTapSeek": "Doppeltippen auf die Bildschirmränder zum Rückwärts-/Vorwärtsspringen",
|
||||||
|
|
||||||
|
@ -576,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Animationen entfernen",
|
"settingsRemoveAnimationsTile": "Animationen entfernen",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Animationen entfernen",
|
"settingsRemoveAnimationsDialogTitle": "Animationen entfernen",
|
||||||
"settingsTimeToTakeActionTile": "Zeit zum Reagieren",
|
"settingsTimeToTakeActionTile": "Zeit zum Reagieren",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Zeit zum Reagieren",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Anzeige",
|
"settingsDisplaySectionTitle": "Anzeige",
|
||||||
"settingsThemeBrightnessTile": "Thema",
|
"settingsThemeBrightnessTile": "Thema",
|
||||||
|
@ -598,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Bilderrahmen",
|
"settingsWidgetPageTitle": "Bilderrahmen",
|
||||||
"settingsWidgetShowOutline": "Gliederung",
|
"settingsWidgetShowOutline": "Gliederung",
|
||||||
|
"settingsWidgetOpenPage": "Beim Tippen auf das Widget",
|
||||||
|
|
||||||
"settingsCollectionTile": "Sammlung",
|
"settingsCollectionTile": "Sammlung",
|
||||||
|
|
||||||
|
@ -606,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Top-Länder",
|
"statsTopCountriesSectionTitle": "Top-Länder",
|
||||||
"statsTopPlacesSectionTitle": "Top-Plätze",
|
"statsTopPlacesSectionTitle": "Top-Plätze",
|
||||||
"statsTopTagsSectionTitle": "Top-Tags",
|
"statsTopTagsSectionTitle": "Top-Tags",
|
||||||
|
"statsTopAlbumsSectionTitle": "Top-Alben",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "ÖFFNE PANORAMA",
|
"viewerOpenPanoramaButtonLabel": "ÖFFNE PANORAMA",
|
||||||
"viewerSetWallpaperButtonLabel": "HINTERGRUNDBILD EINSTELLEN",
|
"viewerSetWallpaperButtonLabel": "HINTERGRUNDBILD EINSTELLEN",
|
||||||
|
@ -650,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Auflösung",
|
"viewerInfoSearchSuggestionResolution": "Auflösung",
|
||||||
"viewerInfoSearchSuggestionRights": "Rechte",
|
"viewerInfoSearchSuggestionRights": "Rechte",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Scroll-Effekt auf dem Startbildschirm verwenden",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Tags bearbeiten",
|
"tagEditorPageTitle": "Tags bearbeiten",
|
||||||
"tagEditorPageNewTagFieldLabel": "Neuer Tag",
|
"tagEditorPageNewTagFieldLabel": "Neuer Tag",
|
||||||
"tagEditorPageAddTagTooltip": "Tag hinzufügen",
|
"tagEditorPageAddTagTooltip": "Tag hinzufügen",
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "Εμφάνιση στα Άλμπουμ",
|
"chipActionGoToAlbumPage": "Εμφάνιση στα Άλμπουμ",
|
||||||
"chipActionGoToCountryPage": "Εμφάνιση στις χώρες",
|
"chipActionGoToCountryPage": "Εμφάνιση στις χώρες",
|
||||||
"chipActionGoToTagPage": "Εμφάνιση στις ετικέτες",
|
"chipActionGoToTagPage": "Εμφάνιση στις ετικέτες",
|
||||||
|
"chipActionFilterOut": "Χωρίς φιλτράρισμα",
|
||||||
|
"chipActionFilterIn": "Με φιλτράρισμα",
|
||||||
"chipActionHide": "Απόκρυψη",
|
"chipActionHide": "Απόκρυψη",
|
||||||
"chipActionPin": "Καρφίτσωμα στην κορυφή",
|
"chipActionPin": "Καρφίτσωμα στην κορυφή",
|
||||||
"chipActionUnpin": "Ξέκαρφίτσωμα από την κορυφή",
|
"chipActionUnpin": "Ξέκαρφίτσωμα από την κορυφή",
|
||||||
|
@ -155,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Υψηλότερος ρυθμός",
|
"displayRefreshRatePreferHighest": "Υψηλότερος ρυθμός",
|
||||||
"displayRefreshRatePreferLowest": "Χαμηλότερος ρυθμός",
|
"displayRefreshRatePreferLowest": "Χαμηλότερος ρυθμός",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Παράλειψη",
|
"videoPlaybackSkip": "Παράλειψη",
|
||||||
"slideshowVideoPlaybackMuted": "Αναπαραγωγή σε σίγαση",
|
"videoPlaybackMuted": "Αναπαραγωγή σε σίγαση",
|
||||||
"slideshowVideoPlaybackWithSound": "Αναπαραγωγή με ήχο",
|
"videoPlaybackWithSound": "Αναπαραγωγή με ήχο",
|
||||||
|
|
||||||
"themeBrightnessLight": "Φωτεινό",
|
"themeBrightnessLight": "Φωτεινό",
|
||||||
"themeBrightnessDark": "Σκούρο",
|
"themeBrightnessDark": "Σκούρο",
|
||||||
|
@ -167,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "Παράλλαξη",
|
"viewerTransitionParallax": "Παράλλαξη",
|
||||||
"viewerTransitionFade": "Ξεθώριασμα",
|
"viewerTransitionFade": "Ξεθώριασμα",
|
||||||
"viewerTransitionZoomIn": "Μεγέθυνση",
|
"viewerTransitionZoomIn": "Μεγέθυνση",
|
||||||
|
"viewerTransitionNone": "Καμία",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Αρχική οθόνη",
|
"wallpaperTargetHome": "Αρχική οθόνη",
|
||||||
"wallpaperTargetLock": "Οθόνη κλειδώματος",
|
"wallpaperTargetLock": "Οθόνη κλειδώματος",
|
||||||
"wallpaperTargetHomeLock": "Αρχική οθόνη και οθόνη κλειδώματος",
|
"wallpaperTargetHomeLock": "Αρχική οθόνη και οθόνη κλειδώματος",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Άνοιγμα αρχικής σελίδας",
|
||||||
|
"widgetOpenPageViewer": "Άνοιγμα προβολέα αρχείων",
|
||||||
|
|
||||||
"albumTierNew": "Νέα",
|
"albumTierNew": "Νέα",
|
||||||
"albumTierPinned": "Καρφιτσωμένα",
|
"albumTierPinned": "Καρφιτσωμένα",
|
||||||
"albumTierSpecial": "Συστήματος",
|
"albumTierSpecial": "Συστήματος",
|
||||||
|
@ -289,6 +295,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "Διαταξη",
|
"viewDialogLayoutSectionTitle": "Διαταξη",
|
||||||
"viewDialogReverseSortOrder": "Αντίστροφη σειρά ταξινόμησης",
|
"viewDialogReverseSortOrder": "Αντίστροφη σειρά ταξινόμησης",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Ψηφιδωτό",
|
||||||
"tileLayoutGrid": "Πλέγμα",
|
"tileLayoutGrid": "Πλέγμα",
|
||||||
"tileLayoutList": "Λίστα",
|
"tileLayoutList": "Λίστα",
|
||||||
|
|
||||||
|
@ -401,9 +408,12 @@
|
||||||
"sortOrderSmallestFirst": "Τα μικρότερα πρώτα",
|
"sortOrderSmallestFirst": "Τα μικρότερα πρώτα",
|
||||||
|
|
||||||
"albumGroupTier": "Ανά βαθμίδα",
|
"albumGroupTier": "Ανά βαθμίδα",
|
||||||
|
"albumGroupType": "Ανά τύπο",
|
||||||
"albumGroupVolume": "Ανά αποθηκευτική μονάδα",
|
"albumGroupVolume": "Ανά αποθηκευτική μονάδα",
|
||||||
"albumGroupNone": "Να μην γίνει ομαδοποίηση",
|
"albumGroupNone": "Να μην γίνει ομαδοποίηση",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Μικτα",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "Αντιγραφή στο άλμπουμ",
|
"albumPickPageTitleCopy": "Αντιγραφή στο άλμπουμ",
|
||||||
"albumPickPageTitleExport": "Εξαγωγή στο άλμπουμ",
|
"albumPickPageTitleExport": "Εξαγωγή στο άλμπουμ",
|
||||||
"albumPickPageTitleMove": "Μετακίνηση στο άλμπουμ",
|
"albumPickPageTitleMove": "Μετακίνηση στο άλμπουμ",
|
||||||
|
@ -442,6 +452,7 @@
|
||||||
"settingsPageTitle": "Ρυθμισεις",
|
"settingsPageTitle": "Ρυθμισεις",
|
||||||
"settingsSystemDefault": "Σύστημα",
|
"settingsSystemDefault": "Σύστημα",
|
||||||
"settingsDefault": "Προεπιλογή",
|
"settingsDefault": "Προεπιλογή",
|
||||||
|
"settingsDisabled": "Απενεργοποιημένο",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Αναζήτηση ρυθμίσεων",
|
"settingsSearchFieldLabel": "Αναζήτηση ρυθμίσεων",
|
||||||
"settingsSearchEmpty": "Δεν υπάρχει αντίστοιχη ρύθμιση",
|
"settingsSearchEmpty": "Δεν υπάρχει αντίστοιχη ρύθμιση",
|
||||||
|
@ -524,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "Επανάληψη",
|
"settingsSlideshowRepeat": "Επανάληψη",
|
||||||
"settingsSlideshowShuffle": "Τυχαία σειρά",
|
"settingsSlideshowShuffle": "Τυχαία σειρά",
|
||||||
"settingsSlideshowFillScreen": "Χρησιμοποίηση πλήρης οθόνης",
|
"settingsSlideshowFillScreen": "Χρησιμοποίηση πλήρης οθόνης",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Εφέ κινούμενου ζουμ",
|
||||||
"settingsSlideshowTransitionTile": "Μετάβαση",
|
"settingsSlideshowTransitionTile": "Μετάβαση",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Μεταβαση",
|
|
||||||
"settingsSlideshowIntervalTile": "Διάρκεια",
|
"settingsSlideshowIntervalTile": "Διάρκεια",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Διαρκεια",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Αναπαραγωγή βίντεο",
|
"settingsSlideshowVideoPlaybackTile": "Αναπαραγωγή βίντεο",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Αναπαραγωγη Βιντεο",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Αναπαραγωγη Βιντεο",
|
||||||
|
|
||||||
|
@ -535,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "Βιντεο",
|
"settingsVideoSectionTitle": "Βιντεο",
|
||||||
"settingsVideoShowVideos": "Εμφάνιση των βίντεο στη συλλογή",
|
"settingsVideoShowVideos": "Εμφάνιση των βίντεο στη συλλογή",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Επιτάχυνση υλισμικού",
|
"settingsVideoEnableHardwareAcceleration": "Επιτάχυνση υλισμικού",
|
||||||
"settingsVideoEnableAutoPlay": "Αυτόματη αναπαραγωγή κατά το άνοιγμα",
|
"settingsVideoAutoPlay": "Αυτόματη αναπαραγωγή κατά το άνοιγμα",
|
||||||
"settingsVideoLoopModeTile": "Επανάληψη αυτόματα στο τέλος κάθε βίντεο",
|
"settingsVideoLoopModeTile": "Επανάληψη αυτόματα στο τέλος κάθε βίντεο",
|
||||||
"settingsVideoLoopModeDialogTitle": "Επαναληψη Αυτοματα στο Τελος Καθε Βιντεο",
|
"settingsVideoLoopModeDialogTitle": "Επαναληψη Αυτοματα στο Τελος Καθε Βιντεο",
|
||||||
|
|
||||||
|
@ -557,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "Έλεγχος",
|
"settingsVideoControlsTile": "Έλεγχος",
|
||||||
"settingsVideoControlsPageTitle": "Ελεγχος",
|
"settingsVideoControlsPageTitle": "Ελεγχος",
|
||||||
"settingsVideoButtonsTile": "Κουμπιά",
|
"settingsVideoButtonsTile": "Κουμπιά",
|
||||||
"settingsVideoButtonsDialogTitle": "Κουμπια",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Αγγίξτε την οθόνη δύο φορές για αναπαραγωγή/παύση",
|
"settingsVideoGestureDoubleTapTogglePlay": "Αγγίξτε την οθόνη δύο φορές για αναπαραγωγή/παύση",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Αγγίξτε δύο φορές τις άκρες της οθόνης για να πάτε πίσω/εμπρός",
|
"settingsVideoGestureSideDoubleTapSeek": "Αγγίξτε δύο φορές τις άκρες της οθόνης για να πάτε πίσω/εμπρός",
|
||||||
|
|
||||||
|
@ -590,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Κατάργηση κινούμενων εικόνων",
|
"settingsRemoveAnimationsTile": "Κατάργηση κινούμενων εικόνων",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Καταργηση Κινουμενων Εικονων",
|
"settingsRemoveAnimationsDialogTitle": "Καταργηση Κινουμενων Εικονων",
|
||||||
"settingsTimeToTakeActionTile": "Χρόνος λήψης ενεργειών",
|
"settingsTimeToTakeActionTile": "Χρόνος λήψης ενεργειών",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Χρονος Ληψης Ενεργειων",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Οθονη",
|
"settingsDisplaySectionTitle": "Οθονη",
|
||||||
"settingsThemeBrightnessTile": "Θέμα",
|
"settingsThemeBrightnessTile": "Θέμα",
|
||||||
|
@ -612,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Κορνιζα",
|
"settingsWidgetPageTitle": "Κορνιζα",
|
||||||
"settingsWidgetShowOutline": "Περίγραμμα",
|
"settingsWidgetShowOutline": "Περίγραμμα",
|
||||||
|
"settingsWidgetOpenPage": "Όταν πατάτε στο γραφικό στοιχείο",
|
||||||
|
|
||||||
"settingsCollectionTile": "Συλλογή",
|
"settingsCollectionTile": "Συλλογή",
|
||||||
|
|
||||||
|
@ -620,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Κορυφαιες Χωρες",
|
"statsTopCountriesSectionTitle": "Κορυφαιες Χωρες",
|
||||||
"statsTopPlacesSectionTitle": "Κορυφαια Μερη",
|
"statsTopPlacesSectionTitle": "Κορυφαια Μερη",
|
||||||
"statsTopTagsSectionTitle": "Κορυφαιες Ετικετες",
|
"statsTopTagsSectionTitle": "Κορυφαιες Ετικετες",
|
||||||
|
"statsTopAlbumsSectionTitle": "Κορυφαια Αλμπουμ",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "Άνοιγμα πανοραμικών",
|
"viewerOpenPanoramaButtonLabel": "Άνοιγμα πανοραμικών",
|
||||||
"viewerSetWallpaperButtonLabel": "ΟΡΙΣΜΟΣ ΤΑΠΕΤΣΑΡΙΑΣ",
|
"viewerSetWallpaperButtonLabel": "ΟΡΙΣΜΟΣ ΤΑΠΕΤΣΑΡΙΑΣ",
|
||||||
|
@ -664,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Ανάλυση",
|
"viewerInfoSearchSuggestionResolution": "Ανάλυση",
|
||||||
"viewerInfoSearchSuggestionRights": "Δικαιώματα",
|
"viewerInfoSearchSuggestionRights": "Δικαιώματα",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Εφέ κύλισης στην αρχική οθόνη",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Επεξεργασια Ετικετων",
|
"tagEditorPageTitle": "Επεξεργασια Ετικετων",
|
||||||
"tagEditorPageNewTagFieldLabel": "Νέα ετικέτα",
|
"tagEditorPageNewTagFieldLabel": "Νέα ετικέτα",
|
||||||
"tagEditorPageAddTagTooltip": "Προσθήκη ετικέτας",
|
"tagEditorPageAddTagTooltip": "Προσθήκη ετικέτας",
|
||||||
|
|
|
@ -69,6 +69,8 @@
|
||||||
"chipActionGoToAlbumPage": "Show in Albums",
|
"chipActionGoToAlbumPage": "Show in Albums",
|
||||||
"chipActionGoToCountryPage": "Show in Countries",
|
"chipActionGoToCountryPage": "Show in Countries",
|
||||||
"chipActionGoToTagPage": "Show in Tags",
|
"chipActionGoToTagPage": "Show in Tags",
|
||||||
|
"chipActionFilterOut": "Filter out",
|
||||||
|
"chipActionFilterIn": "Filter in",
|
||||||
"chipActionHide": "Hide",
|
"chipActionHide": "Hide",
|
||||||
"chipActionPin": "Pin to top",
|
"chipActionPin": "Pin to top",
|
||||||
"chipActionUnpin": "Unpin from top",
|
"chipActionUnpin": "Unpin from top",
|
||||||
|
@ -195,9 +197,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Highest rate",
|
"displayRefreshRatePreferHighest": "Highest rate",
|
||||||
"displayRefreshRatePreferLowest": "Lowest rate",
|
"displayRefreshRatePreferLowest": "Lowest rate",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Skip",
|
"videoPlaybackSkip": "Skip",
|
||||||
"slideshowVideoPlaybackMuted": "Play muted",
|
"videoPlaybackMuted": "Play muted",
|
||||||
"slideshowVideoPlaybackWithSound": "Play with sound",
|
"videoPlaybackWithSound": "Play with sound",
|
||||||
|
|
||||||
"themeBrightnessLight": "Light",
|
"themeBrightnessLight": "Light",
|
||||||
"themeBrightnessDark": "Dark",
|
"themeBrightnessDark": "Dark",
|
||||||
|
@ -207,11 +209,15 @@
|
||||||
"viewerTransitionParallax": "Parallax",
|
"viewerTransitionParallax": "Parallax",
|
||||||
"viewerTransitionFade": "Fade",
|
"viewerTransitionFade": "Fade",
|
||||||
"viewerTransitionZoomIn": "Zoom in",
|
"viewerTransitionZoomIn": "Zoom in",
|
||||||
|
"viewerTransitionNone": "None",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Home screen",
|
"wallpaperTargetHome": "Home screen",
|
||||||
"wallpaperTargetLock": "Lock screen",
|
"wallpaperTargetLock": "Lock screen",
|
||||||
"wallpaperTargetHomeLock": "Home and lock screens",
|
"wallpaperTargetHomeLock": "Home and lock screens",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Open home",
|
||||||
|
"widgetOpenPageViewer": "Open viewer",
|
||||||
|
|
||||||
"albumTierNew": "New",
|
"albumTierNew": "New",
|
||||||
"albumTierPinned": "Pinned",
|
"albumTierPinned": "Pinned",
|
||||||
"albumTierSpecial": "Common",
|
"albumTierSpecial": "Common",
|
||||||
|
@ -419,6 +425,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "Layout",
|
"viewDialogLayoutSectionTitle": "Layout",
|
||||||
"viewDialogReverseSortOrder": "Reverse sort order",
|
"viewDialogReverseSortOrder": "Reverse sort order",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Mosaic",
|
||||||
"tileLayoutGrid": "Grid",
|
"tileLayoutGrid": "Grid",
|
||||||
"tileLayoutList": "List",
|
"tileLayoutList": "List",
|
||||||
|
|
||||||
|
@ -581,9 +588,12 @@
|
||||||
"sortOrderSmallestFirst": "Smallest first",
|
"sortOrderSmallestFirst": "Smallest first",
|
||||||
|
|
||||||
"albumGroupTier": "By tier",
|
"albumGroupTier": "By tier",
|
||||||
|
"albumGroupType": "By type",
|
||||||
"albumGroupVolume": "By storage volume",
|
"albumGroupVolume": "By storage volume",
|
||||||
"albumGroupNone": "Do not group",
|
"albumGroupNone": "Do not group",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Mixed",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "Copy to Album",
|
"albumPickPageTitleCopy": "Copy to Album",
|
||||||
"albumPickPageTitleExport": "Export to Album",
|
"albumPickPageTitleExport": "Export to Album",
|
||||||
"albumPickPageTitleMove": "Move to Album",
|
"albumPickPageTitleMove": "Move to Album",
|
||||||
|
@ -620,8 +630,9 @@
|
||||||
"searchMetadataSectionTitle": "Metadata",
|
"searchMetadataSectionTitle": "Metadata",
|
||||||
|
|
||||||
"settingsPageTitle": "Settings",
|
"settingsPageTitle": "Settings",
|
||||||
"settingsSystemDefault": "System",
|
"settingsSystemDefault": "System default",
|
||||||
"settingsDefault": "Default",
|
"settingsDefault": "Default",
|
||||||
|
"settingsDisabled": "Disabled",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Search settings",
|
"settingsSearchFieldLabel": "Search settings",
|
||||||
"settingsSearchEmpty": "No matching setting",
|
"settingsSearchEmpty": "No matching setting",
|
||||||
|
@ -704,10 +715,9 @@
|
||||||
"settingsSlideshowRepeat": "Repeat",
|
"settingsSlideshowRepeat": "Repeat",
|
||||||
"settingsSlideshowShuffle": "Shuffle",
|
"settingsSlideshowShuffle": "Shuffle",
|
||||||
"settingsSlideshowFillScreen": "Fill screen",
|
"settingsSlideshowFillScreen": "Fill screen",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Animated zoom effect",
|
||||||
"settingsSlideshowTransitionTile": "Transition",
|
"settingsSlideshowTransitionTile": "Transition",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Transition",
|
|
||||||
"settingsSlideshowIntervalTile": "Interval",
|
"settingsSlideshowIntervalTile": "Interval",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Interval",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Video playback",
|
"settingsSlideshowVideoPlaybackTile": "Video playback",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Video Playback",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Video Playback",
|
||||||
|
|
||||||
|
@ -715,7 +725,7 @@
|
||||||
"settingsVideoSectionTitle": "Video",
|
"settingsVideoSectionTitle": "Video",
|
||||||
"settingsVideoShowVideos": "Show videos",
|
"settingsVideoShowVideos": "Show videos",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Hardware acceleration",
|
"settingsVideoEnableHardwareAcceleration": "Hardware acceleration",
|
||||||
"settingsVideoEnableAutoPlay": "Auto play",
|
"settingsVideoAutoPlay": "Auto play",
|
||||||
"settingsVideoLoopModeTile": "Loop mode",
|
"settingsVideoLoopModeTile": "Loop mode",
|
||||||
"settingsVideoLoopModeDialogTitle": "Loop Mode",
|
"settingsVideoLoopModeDialogTitle": "Loop Mode",
|
||||||
|
|
||||||
|
@ -737,7 +747,6 @@
|
||||||
"settingsVideoControlsTile": "Controls",
|
"settingsVideoControlsTile": "Controls",
|
||||||
"settingsVideoControlsPageTitle": "Controls",
|
"settingsVideoControlsPageTitle": "Controls",
|
||||||
"settingsVideoButtonsTile": "Buttons",
|
"settingsVideoButtonsTile": "Buttons",
|
||||||
"settingsVideoButtonsDialogTitle": "Buttons",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Double tap to play/pause",
|
"settingsVideoGestureDoubleTapTogglePlay": "Double tap to play/pause",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Double tap on screen edges to seek backward/forward",
|
"settingsVideoGestureSideDoubleTapSeek": "Double tap on screen edges to seek backward/forward",
|
||||||
|
|
||||||
|
@ -770,7 +779,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Remove animations",
|
"settingsRemoveAnimationsTile": "Remove animations",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Remove Animations",
|
"settingsRemoveAnimationsDialogTitle": "Remove Animations",
|
||||||
"settingsTimeToTakeActionTile": "Time to take action",
|
"settingsTimeToTakeActionTile": "Time to take action",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Time to Take Action",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Display",
|
"settingsDisplaySectionTitle": "Display",
|
||||||
"settingsThemeBrightnessTile": "Theme",
|
"settingsThemeBrightnessTile": "Theme",
|
||||||
|
@ -792,6 +800,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Photo Frame",
|
"settingsWidgetPageTitle": "Photo Frame",
|
||||||
"settingsWidgetShowOutline": "Outline",
|
"settingsWidgetShowOutline": "Outline",
|
||||||
|
"settingsWidgetOpenPage": "When tapping on the widget",
|
||||||
|
|
||||||
"settingsCollectionTile": "Collection",
|
"settingsCollectionTile": "Collection",
|
||||||
|
|
||||||
|
@ -805,6 +814,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Top Countries",
|
"statsTopCountriesSectionTitle": "Top Countries",
|
||||||
"statsTopPlacesSectionTitle": "Top Places",
|
"statsTopPlacesSectionTitle": "Top Places",
|
||||||
"statsTopTagsSectionTitle": "Top Tags",
|
"statsTopTagsSectionTitle": "Top Tags",
|
||||||
|
"statsTopAlbumsSectionTitle": "Top Albums",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "OPEN PANORAMA",
|
"viewerOpenPanoramaButtonLabel": "OPEN PANORAMA",
|
||||||
"viewerSetWallpaperButtonLabel": "SET WALLPAPER",
|
"viewerSetWallpaperButtonLabel": "SET WALLPAPER",
|
||||||
|
@ -849,6 +859,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Resolution",
|
"viewerInfoSearchSuggestionResolution": "Resolution",
|
||||||
"viewerInfoSearchSuggestionRights": "Rights",
|
"viewerInfoSearchSuggestionRights": "Rights",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Use scroll effect on home screen",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Edit Tags",
|
"tagEditorPageTitle": "Edit Tags",
|
||||||
"tagEditorPageNewTagFieldLabel": "New tag",
|
"tagEditorPageNewTagFieldLabel": "New tag",
|
||||||
"tagEditorPageAddTagTooltip": "Add tag",
|
"tagEditorPageAddTagTooltip": "Add tag",
|
||||||
|
|
|
@ -151,9 +151,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Alta tasa",
|
"displayRefreshRatePreferHighest": "Alta tasa",
|
||||||
"displayRefreshRatePreferLowest": "Baja tasa",
|
"displayRefreshRatePreferLowest": "Baja tasa",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Saltear",
|
"videoPlaybackSkip": "Saltear",
|
||||||
"slideshowVideoPlaybackMuted": "Reproducir sin sonido",
|
"videoPlaybackMuted": "Reproducir sin sonido",
|
||||||
"slideshowVideoPlaybackWithSound": "Reproducir con sonido",
|
"videoPlaybackWithSound": "Reproducir con sonido",
|
||||||
|
|
||||||
"themeBrightnessLight": "Claro",
|
"themeBrightnessLight": "Claro",
|
||||||
"themeBrightnessDark": "Obscuro",
|
"themeBrightnessDark": "Obscuro",
|
||||||
|
@ -509,9 +509,7 @@
|
||||||
"settingsSlideshowShuffle": "Mezclar",
|
"settingsSlideshowShuffle": "Mezclar",
|
||||||
"settingsSlideshowFillScreen": "Llenar pantalla",
|
"settingsSlideshowFillScreen": "Llenar pantalla",
|
||||||
"settingsSlideshowTransitionTile": "Transición",
|
"settingsSlideshowTransitionTile": "Transición",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Transición",
|
|
||||||
"settingsSlideshowIntervalTile": "Intervalo",
|
"settingsSlideshowIntervalTile": "Intervalo",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Intervalo",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Reproducción de video",
|
"settingsSlideshowVideoPlaybackTile": "Reproducción de video",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Reproducción de video",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Reproducción de video",
|
||||||
|
|
||||||
|
@ -519,7 +517,7 @@
|
||||||
"settingsVideoSectionTitle": "Video",
|
"settingsVideoSectionTitle": "Video",
|
||||||
"settingsVideoShowVideos": "Mostrar videos",
|
"settingsVideoShowVideos": "Mostrar videos",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Aceleración por hardware",
|
"settingsVideoEnableHardwareAcceleration": "Aceleración por hardware",
|
||||||
"settingsVideoEnableAutoPlay": "Reproducción automática",
|
"settingsVideoAutoPlay": "Reproducción automática",
|
||||||
"settingsVideoLoopModeTile": "Modo bucle",
|
"settingsVideoLoopModeTile": "Modo bucle",
|
||||||
"settingsVideoLoopModeDialogTitle": "Modo bucle",
|
"settingsVideoLoopModeDialogTitle": "Modo bucle",
|
||||||
|
|
||||||
|
@ -541,7 +539,6 @@
|
||||||
"settingsVideoControlsTile": "Controles",
|
"settingsVideoControlsTile": "Controles",
|
||||||
"settingsVideoControlsPageTitle": "Controles",
|
"settingsVideoControlsPageTitle": "Controles",
|
||||||
"settingsVideoButtonsTile": "Botones",
|
"settingsVideoButtonsTile": "Botones",
|
||||||
"settingsVideoButtonsDialogTitle": "Botones",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Doble toque para reproducir/pausar",
|
"settingsVideoGestureDoubleTapTogglePlay": "Doble toque para reproducir/pausar",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Doble toque en los bordes de la pantalla para buscar atrás/adelante",
|
"settingsVideoGestureSideDoubleTapSeek": "Doble toque en los bordes de la pantalla para buscar atrás/adelante",
|
||||||
|
|
||||||
|
@ -574,7 +571,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Remover animaciones",
|
"settingsRemoveAnimationsTile": "Remover animaciones",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Remover animaciones",
|
"settingsRemoveAnimationsDialogTitle": "Remover animaciones",
|
||||||
"settingsTimeToTakeActionTile": "Retraso para ejecutar una acción",
|
"settingsTimeToTakeActionTile": "Retraso para ejecutar una acción",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Retraso para ejecutar una acción",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Pantalla",
|
"settingsDisplaySectionTitle": "Pantalla",
|
||||||
"settingsThemeBrightnessTile": "Tema",
|
"settingsThemeBrightnessTile": "Tema",
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "Afficher dans Albums",
|
"chipActionGoToAlbumPage": "Afficher dans Albums",
|
||||||
"chipActionGoToCountryPage": "Afficher dans Pays",
|
"chipActionGoToCountryPage": "Afficher dans Pays",
|
||||||
"chipActionGoToTagPage": "Afficher dans Libellés",
|
"chipActionGoToTagPage": "Afficher dans Libellés",
|
||||||
|
"chipActionFilterOut": "Exclure",
|
||||||
|
"chipActionFilterIn": "Inclure",
|
||||||
"chipActionHide": "Masquer",
|
"chipActionHide": "Masquer",
|
||||||
"chipActionPin": "Épingler",
|
"chipActionPin": "Épingler",
|
||||||
"chipActionUnpin": "Retirer",
|
"chipActionUnpin": "Retirer",
|
||||||
|
@ -155,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Fréquence maximale",
|
"displayRefreshRatePreferHighest": "Fréquence maximale",
|
||||||
"displayRefreshRatePreferLowest": "Fréquence minimale",
|
"displayRefreshRatePreferLowest": "Fréquence minimale",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Passer",
|
"videoPlaybackSkip": "Passer",
|
||||||
"slideshowVideoPlaybackMuted": "Jouer sans son",
|
"videoPlaybackMuted": "Jouer sans son",
|
||||||
"slideshowVideoPlaybackWithSound": "Jouer avec son",
|
"videoPlaybackWithSound": "Jouer avec son",
|
||||||
|
|
||||||
"themeBrightnessLight": "Clair",
|
"themeBrightnessLight": "Clair",
|
||||||
"themeBrightnessDark": "Sombre",
|
"themeBrightnessDark": "Sombre",
|
||||||
|
@ -167,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "Parallaxe",
|
"viewerTransitionParallax": "Parallaxe",
|
||||||
"viewerTransitionFade": "Fondu",
|
"viewerTransitionFade": "Fondu",
|
||||||
"viewerTransitionZoomIn": "Zoom",
|
"viewerTransitionZoomIn": "Zoom",
|
||||||
|
"viewerTransitionNone": "Aucune",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Écran d’accueil",
|
"wallpaperTargetHome": "Écran d’accueil",
|
||||||
"wallpaperTargetLock": "Écran de verrouillage",
|
"wallpaperTargetLock": "Écran de verrouillage",
|
||||||
"wallpaperTargetHomeLock": "Écrans accueil et verrouillage",
|
"wallpaperTargetHomeLock": "Écrans accueil et verrouillage",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Ouvrir la page d’accueil",
|
||||||
|
"widgetOpenPageViewer": "Ouvrir la visionneuse",
|
||||||
|
|
||||||
"albumTierNew": "Nouveaux",
|
"albumTierNew": "Nouveaux",
|
||||||
"albumTierPinned": "Épinglés",
|
"albumTierPinned": "Épinglés",
|
||||||
"albumTierSpecial": "Standards",
|
"albumTierSpecial": "Standards",
|
||||||
|
@ -289,6 +295,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "Vue",
|
"viewDialogLayoutSectionTitle": "Vue",
|
||||||
"viewDialogReverseSortOrder": "Inverser l’ordre",
|
"viewDialogReverseSortOrder": "Inverser l’ordre",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Mosaïque",
|
||||||
"tileLayoutGrid": "Grille",
|
"tileLayoutGrid": "Grille",
|
||||||
"tileLayoutList": "Liste",
|
"tileLayoutList": "Liste",
|
||||||
|
|
||||||
|
@ -401,9 +408,12 @@
|
||||||
"sortOrderSmallestFirst": "Moins larges d’abord",
|
"sortOrderSmallestFirst": "Moins larges d’abord",
|
||||||
|
|
||||||
"albumGroupTier": "par importance",
|
"albumGroupTier": "par importance",
|
||||||
|
"albumGroupType": "par type",
|
||||||
"albumGroupVolume": "par volume de stockage",
|
"albumGroupVolume": "par volume de stockage",
|
||||||
"albumGroupNone": "ne pas grouper",
|
"albumGroupNone": "ne pas grouper",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Mixte",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "Copie",
|
"albumPickPageTitleCopy": "Copie",
|
||||||
"albumPickPageTitleExport": "Export",
|
"albumPickPageTitleExport": "Export",
|
||||||
"albumPickPageTitleMove": "Déplacement",
|
"albumPickPageTitleMove": "Déplacement",
|
||||||
|
@ -442,6 +452,7 @@
|
||||||
"settingsPageTitle": "Réglages",
|
"settingsPageTitle": "Réglages",
|
||||||
"settingsSystemDefault": "Système",
|
"settingsSystemDefault": "Système",
|
||||||
"settingsDefault": "Par défaut",
|
"settingsDefault": "Par défaut",
|
||||||
|
"settingsDisabled": "Désactivé",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Recherche de réglages",
|
"settingsSearchFieldLabel": "Recherche de réglages",
|
||||||
"settingsSearchEmpty": "Aucun réglage correspondant",
|
"settingsSearchEmpty": "Aucun réglage correspondant",
|
||||||
|
@ -524,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "Répéter",
|
"settingsSlideshowRepeat": "Répéter",
|
||||||
"settingsSlideshowShuffle": "Aléatoire",
|
"settingsSlideshowShuffle": "Aléatoire",
|
||||||
"settingsSlideshowFillScreen": "Remplir l’écran",
|
"settingsSlideshowFillScreen": "Remplir l’écran",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Effet de zoom animé",
|
||||||
"settingsSlideshowTransitionTile": "Transition",
|
"settingsSlideshowTransitionTile": "Transition",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Transition",
|
|
||||||
"settingsSlideshowIntervalTile": "Intervalle",
|
"settingsSlideshowIntervalTile": "Intervalle",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Intervalle",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Lecture de vidéos",
|
"settingsSlideshowVideoPlaybackTile": "Lecture de vidéos",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Lecture de vidéos",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Lecture de vidéos",
|
||||||
|
|
||||||
|
@ -535,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "Vidéo",
|
"settingsVideoSectionTitle": "Vidéo",
|
||||||
"settingsVideoShowVideos": "Afficher les vidéos",
|
"settingsVideoShowVideos": "Afficher les vidéos",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Accélération matérielle",
|
"settingsVideoEnableHardwareAcceleration": "Accélération matérielle",
|
||||||
"settingsVideoEnableAutoPlay": "Lecture automatique",
|
"settingsVideoAutoPlay": "Lecture automatique",
|
||||||
"settingsVideoLoopModeTile": "Lecture répétée",
|
"settingsVideoLoopModeTile": "Lecture répétée",
|
||||||
"settingsVideoLoopModeDialogTitle": "Lecture répétée",
|
"settingsVideoLoopModeDialogTitle": "Lecture répétée",
|
||||||
|
|
||||||
|
@ -557,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "Contrôles",
|
"settingsVideoControlsTile": "Contrôles",
|
||||||
"settingsVideoControlsPageTitle": "Contrôles",
|
"settingsVideoControlsPageTitle": "Contrôles",
|
||||||
"settingsVideoButtonsTile": "Boutons",
|
"settingsVideoButtonsTile": "Boutons",
|
||||||
"settingsVideoButtonsDialogTitle": "Boutons",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Appuyer deux fois pour lire ou mettre en pause",
|
"settingsVideoGestureDoubleTapTogglePlay": "Appuyer deux fois pour lire ou mettre en pause",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Appuyer deux fois sur les bords de l’écran pour reculer ou avancer",
|
"settingsVideoGestureSideDoubleTapSeek": "Appuyer deux fois sur les bords de l’écran pour reculer ou avancer",
|
||||||
|
|
||||||
|
@ -590,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Suppression des animations",
|
"settingsRemoveAnimationsTile": "Suppression des animations",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Suppression des animations",
|
"settingsRemoveAnimationsDialogTitle": "Suppression des animations",
|
||||||
"settingsTimeToTakeActionTile": "Délai pour effectuer une action",
|
"settingsTimeToTakeActionTile": "Délai pour effectuer une action",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Délai pour effectuer une action",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Affichage",
|
"settingsDisplaySectionTitle": "Affichage",
|
||||||
"settingsThemeBrightnessTile": "Thème",
|
"settingsThemeBrightnessTile": "Thème",
|
||||||
|
@ -612,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Cadre photo",
|
"settingsWidgetPageTitle": "Cadre photo",
|
||||||
"settingsWidgetShowOutline": "Contours",
|
"settingsWidgetShowOutline": "Contours",
|
||||||
|
"settingsWidgetOpenPage": "Quand vous appuyez sur le widget",
|
||||||
|
|
||||||
"settingsCollectionTile": "Collection",
|
"settingsCollectionTile": "Collection",
|
||||||
|
|
||||||
|
@ -620,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Top pays",
|
"statsTopCountriesSectionTitle": "Top pays",
|
||||||
"statsTopPlacesSectionTitle": "Top lieux",
|
"statsTopPlacesSectionTitle": "Top lieux",
|
||||||
"statsTopTagsSectionTitle": "Top libellés",
|
"statsTopTagsSectionTitle": "Top libellés",
|
||||||
|
"statsTopAlbumsSectionTitle": "Top albums",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "OUVRIR LE PANORAMA",
|
"viewerOpenPanoramaButtonLabel": "OUVRIR LE PANORAMA",
|
||||||
"viewerSetWallpaperButtonLabel": "APPLIQUER",
|
"viewerSetWallpaperButtonLabel": "APPLIQUER",
|
||||||
|
@ -664,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Résolution",
|
"viewerInfoSearchSuggestionResolution": "Résolution",
|
||||||
"viewerInfoSearchSuggestionRights": "Droits",
|
"viewerInfoSearchSuggestionRights": "Droits",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Utiliser l’effet de défilement sur l’écran d’accueil",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Modifier les libellés",
|
"tagEditorPageTitle": "Modifier les libellés",
|
||||||
"tagEditorPageNewTagFieldLabel": "Nouveau libellé",
|
"tagEditorPageNewTagFieldLabel": "Nouveau libellé",
|
||||||
"tagEditorPageAddTagTooltip": "Ajouter le libellé",
|
"tagEditorPageAddTagTooltip": "Ajouter le libellé",
|
||||||
|
|
|
@ -155,9 +155,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Penyegaran tertinggi",
|
"displayRefreshRatePreferHighest": "Penyegaran tertinggi",
|
||||||
"displayRefreshRatePreferLowest": "Penyegaran terendah",
|
"displayRefreshRatePreferLowest": "Penyegaran terendah",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Lewati",
|
"videoPlaybackSkip": "Lewati",
|
||||||
"slideshowVideoPlaybackMuted": "Mainkan bisu",
|
"videoPlaybackMuted": "Mainkan bisu",
|
||||||
"slideshowVideoPlaybackWithSound": "Mainkan dengan suara",
|
"videoPlaybackWithSound": "Mainkan dengan suara",
|
||||||
|
|
||||||
"themeBrightnessLight": "Terang",
|
"themeBrightnessLight": "Terang",
|
||||||
"themeBrightnessDark": "Gelap",
|
"themeBrightnessDark": "Gelap",
|
||||||
|
@ -525,9 +525,7 @@
|
||||||
"settingsSlideshowShuffle": "Acak",
|
"settingsSlideshowShuffle": "Acak",
|
||||||
"settingsSlideshowFillScreen": "Isi layar",
|
"settingsSlideshowFillScreen": "Isi layar",
|
||||||
"settingsSlideshowTransitionTile": "Transisi",
|
"settingsSlideshowTransitionTile": "Transisi",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Transisi",
|
|
||||||
"settingsSlideshowIntervalTile": "Interval",
|
"settingsSlideshowIntervalTile": "Interval",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Interval",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Putaran ulang video",
|
"settingsSlideshowVideoPlaybackTile": "Putaran ulang video",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Putaran Ulang Video",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Putaran Ulang Video",
|
||||||
|
|
||||||
|
@ -535,7 +533,7 @@
|
||||||
"settingsVideoSectionTitle": "Video",
|
"settingsVideoSectionTitle": "Video",
|
||||||
"settingsVideoShowVideos": "Tampilkan video",
|
"settingsVideoShowVideos": "Tampilkan video",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Akselerasi perangkat keras",
|
"settingsVideoEnableHardwareAcceleration": "Akselerasi perangkat keras",
|
||||||
"settingsVideoEnableAutoPlay": "Putar otomatis",
|
"settingsVideoAutoPlay": "Putar otomatis",
|
||||||
"settingsVideoLoopModeTile": "Putar ulang",
|
"settingsVideoLoopModeTile": "Putar ulang",
|
||||||
"settingsVideoLoopModeDialogTitle": "Putar Ulang",
|
"settingsVideoLoopModeDialogTitle": "Putar Ulang",
|
||||||
|
|
||||||
|
@ -557,7 +555,6 @@
|
||||||
"settingsVideoControlsTile": "Kontrol",
|
"settingsVideoControlsTile": "Kontrol",
|
||||||
"settingsVideoControlsPageTitle": "Kontrol",
|
"settingsVideoControlsPageTitle": "Kontrol",
|
||||||
"settingsVideoButtonsTile": "Tombol",
|
"settingsVideoButtonsTile": "Tombol",
|
||||||
"settingsVideoButtonsDialogTitle": "Tombol",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Ketuk dua kali untuk mainkan/hentikan",
|
"settingsVideoGestureDoubleTapTogglePlay": "Ketuk dua kali untuk mainkan/hentikan",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Ketuk dua kali di tepi layar untuk mencari kebelakang/kedepan",
|
"settingsVideoGestureSideDoubleTapSeek": "Ketuk dua kali di tepi layar untuk mencari kebelakang/kedepan",
|
||||||
|
|
||||||
|
@ -590,7 +587,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Hapus animasi",
|
"settingsRemoveAnimationsTile": "Hapus animasi",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Hapus Animasi",
|
"settingsRemoveAnimationsDialogTitle": "Hapus Animasi",
|
||||||
"settingsTimeToTakeActionTile": "Waktu untuk mengambil tindakan",
|
"settingsTimeToTakeActionTile": "Waktu untuk mengambil tindakan",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Saatnya Bertindak",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Tampilan",
|
"settingsDisplaySectionTitle": "Tampilan",
|
||||||
"settingsThemeBrightnessTile": "Tema",
|
"settingsThemeBrightnessTile": "Tema",
|
||||||
|
|
|
@ -40,7 +40,9 @@
|
||||||
"chipActionDelete": "Elimina",
|
"chipActionDelete": "Elimina",
|
||||||
"chipActionGoToAlbumPage": "Mostra negli album",
|
"chipActionGoToAlbumPage": "Mostra negli album",
|
||||||
"chipActionGoToCountryPage": "Mostra nei Paesi",
|
"chipActionGoToCountryPage": "Mostra nei Paesi",
|
||||||
"chipActionGoToTagPage": "Mostra nelle etichette",
|
"chipActionGoToTagPage": "Mostra nelle Etichette",
|
||||||
|
"chipActionFilterOut": "Escludi",
|
||||||
|
"chipActionFilterIn": "Includi",
|
||||||
"chipActionHide": "Nascondi",
|
"chipActionHide": "Nascondi",
|
||||||
"chipActionPin": "Fissa in alto",
|
"chipActionPin": "Fissa in alto",
|
||||||
"chipActionUnpin": "Rimuovi dall’alto",
|
"chipActionUnpin": "Rimuovi dall’alto",
|
||||||
|
@ -155,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Frequenza massima",
|
"displayRefreshRatePreferHighest": "Frequenza massima",
|
||||||
"displayRefreshRatePreferLowest": "Frequenza minima",
|
"displayRefreshRatePreferLowest": "Frequenza minima",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Salta",
|
"videoPlaybackSkip": "Salta",
|
||||||
"slideshowVideoPlaybackMuted": "Riproduci senza audio",
|
"videoPlaybackMuted": "Riproduci senza audio",
|
||||||
"slideshowVideoPlaybackWithSound": "Riproduci con audio",
|
"videoPlaybackWithSound": "Riproduci con audio",
|
||||||
|
|
||||||
"themeBrightnessLight": "Chiaro",
|
"themeBrightnessLight": "Chiaro",
|
||||||
"themeBrightnessDark": "Scuro",
|
"themeBrightnessDark": "Scuro",
|
||||||
|
@ -167,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "Parallasse",
|
"viewerTransitionParallax": "Parallasse",
|
||||||
"viewerTransitionFade": "Dissolvenza",
|
"viewerTransitionFade": "Dissolvenza",
|
||||||
"viewerTransitionZoomIn": "Ingrandisci",
|
"viewerTransitionZoomIn": "Ingrandisci",
|
||||||
|
"viewerTransitionNone": "Nessuna",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Schermata iniziale",
|
"wallpaperTargetHome": "Schermata iniziale",
|
||||||
"wallpaperTargetLock": "Schermata di blocco",
|
"wallpaperTargetLock": "Schermata di blocco",
|
||||||
"wallpaperTargetHomeLock": "Schermata iniziale e di blocco",
|
"wallpaperTargetHomeLock": "Schermata iniziale e di blocco",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Apri pagina iniziale",
|
||||||
|
"widgetOpenPageViewer": "Apri visualizzazione",
|
||||||
|
|
||||||
"albumTierNew": "Nuovi",
|
"albumTierNew": "Nuovi",
|
||||||
"albumTierPinned": "Fissati",
|
"albumTierPinned": "Fissati",
|
||||||
"albumTierSpecial": "Frequenti",
|
"albumTierSpecial": "Frequenti",
|
||||||
|
@ -289,6 +295,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "Layout",
|
"viewDialogLayoutSectionTitle": "Layout",
|
||||||
"viewDialogReverseSortOrder": "Inverti ordinamento",
|
"viewDialogReverseSortOrder": "Inverti ordinamento",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Mosaico",
|
||||||
"tileLayoutGrid": "Griglia",
|
"tileLayoutGrid": "Griglia",
|
||||||
"tileLayoutList": "Lista",
|
"tileLayoutList": "Lista",
|
||||||
|
|
||||||
|
@ -390,6 +397,7 @@
|
||||||
"sortBySize": "Per dimensione",
|
"sortBySize": "Per dimensione",
|
||||||
"sortByAlbumFileName": "Per album e nome del file",
|
"sortByAlbumFileName": "Per album e nome del file",
|
||||||
"sortByRating": "Per valutazione",
|
"sortByRating": "Per valutazione",
|
||||||
|
|
||||||
"sortOrderNewestFirst": "Prima i più nuovi",
|
"sortOrderNewestFirst": "Prima i più nuovi",
|
||||||
"sortOrderOldestFirst": "Prima i più vecchi",
|
"sortOrderOldestFirst": "Prima i più vecchi",
|
||||||
"sortOrderAtoZ": "Dalla A alla Z",
|
"sortOrderAtoZ": "Dalla A alla Z",
|
||||||
|
@ -400,9 +408,12 @@
|
||||||
"sortOrderSmallestFirst": "Prima i più piccoli",
|
"sortOrderSmallestFirst": "Prima i più piccoli",
|
||||||
|
|
||||||
"albumGroupTier": "Per importanza",
|
"albumGroupTier": "Per importanza",
|
||||||
|
"albumGroupType": "Per tipo",
|
||||||
"albumGroupVolume": "Per volume di archiviazione",
|
"albumGroupVolume": "Per volume di archiviazione",
|
||||||
"albumGroupNone": "Non raggruppare",
|
"albumGroupNone": "Non raggruppare",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Misto",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "Copia",
|
"albumPickPageTitleCopy": "Copia",
|
||||||
"albumPickPageTitleExport": "Esporta",
|
"albumPickPageTitleExport": "Esporta",
|
||||||
"albumPickPageTitleMove": "Sposta",
|
"albumPickPageTitleMove": "Sposta",
|
||||||
|
@ -441,6 +452,7 @@
|
||||||
"settingsPageTitle": "Impostazioni",
|
"settingsPageTitle": "Impostazioni",
|
||||||
"settingsSystemDefault": "Sistema",
|
"settingsSystemDefault": "Sistema",
|
||||||
"settingsDefault": "Predefinite",
|
"settingsDefault": "Predefinite",
|
||||||
|
"settingsDisabled": "Disabilitato",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Ricerca impostazioni",
|
"settingsSearchFieldLabel": "Ricerca impostazioni",
|
||||||
"settingsSearchEmpty": "Nessuna impostazione corrispondente",
|
"settingsSearchEmpty": "Nessuna impostazione corrispondente",
|
||||||
|
@ -523,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "Ripeti",
|
"settingsSlideshowRepeat": "Ripeti",
|
||||||
"settingsSlideshowShuffle": "Ordine casuale",
|
"settingsSlideshowShuffle": "Ordine casuale",
|
||||||
"settingsSlideshowFillScreen": "Riempi schermo",
|
"settingsSlideshowFillScreen": "Riempi schermo",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Effetto ingrandimento animato",
|
||||||
"settingsSlideshowTransitionTile": "Transizione",
|
"settingsSlideshowTransitionTile": "Transizione",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Transizione",
|
|
||||||
"settingsSlideshowIntervalTile": "Intervallo",
|
"settingsSlideshowIntervalTile": "Intervallo",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Intervallo",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Riproduzione video",
|
"settingsSlideshowVideoPlaybackTile": "Riproduzione video",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Riproduzione video",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Riproduzione video",
|
||||||
|
|
||||||
|
@ -534,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "Video",
|
"settingsVideoSectionTitle": "Video",
|
||||||
"settingsVideoShowVideos": "Mostra video",
|
"settingsVideoShowVideos": "Mostra video",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Accelerazione hardware",
|
"settingsVideoEnableHardwareAcceleration": "Accelerazione hardware",
|
||||||
"settingsVideoEnableAutoPlay": "Riproduzione automatica",
|
"settingsVideoAutoPlay": "Riproduzione automatica",
|
||||||
"settingsVideoLoopModeTile": "Modalità loop",
|
"settingsVideoLoopModeTile": "Modalità loop",
|
||||||
"settingsVideoLoopModeDialogTitle": "Modalità loop",
|
"settingsVideoLoopModeDialogTitle": "Modalità loop",
|
||||||
|
|
||||||
|
@ -556,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "Controlli",
|
"settingsVideoControlsTile": "Controlli",
|
||||||
"settingsVideoControlsPageTitle": "Controlli",
|
"settingsVideoControlsPageTitle": "Controlli",
|
||||||
"settingsVideoButtonsTile": "Pulsanti",
|
"settingsVideoButtonsTile": "Pulsanti",
|
||||||
"settingsVideoButtonsDialogTitle": "Pulsanti",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Doppio tocco per play/pausa",
|
"settingsVideoGestureDoubleTapTogglePlay": "Doppio tocco per play/pausa",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Doppio tocco sui bordi dello schermo per cercare avanti/indietro",
|
"settingsVideoGestureSideDoubleTapSeek": "Doppio tocco sui bordi dello schermo per cercare avanti/indietro",
|
||||||
|
|
||||||
|
@ -589,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Rimuovi animazioni",
|
"settingsRemoveAnimationsTile": "Rimuovi animazioni",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Rimuovi animazioni",
|
"settingsRemoveAnimationsDialogTitle": "Rimuovi animazioni",
|
||||||
"settingsTimeToTakeActionTile": "Tempo di reazione",
|
"settingsTimeToTakeActionTile": "Tempo di reazione",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Tempo di reazione",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Schermo",
|
"settingsDisplaySectionTitle": "Schermo",
|
||||||
"settingsThemeBrightnessTile": "Tema",
|
"settingsThemeBrightnessTile": "Tema",
|
||||||
|
@ -611,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Cornice foto",
|
"settingsWidgetPageTitle": "Cornice foto",
|
||||||
"settingsWidgetShowOutline": "Contorno",
|
"settingsWidgetShowOutline": "Contorno",
|
||||||
|
"settingsWidgetOpenPage": "Se tocchi il widget",
|
||||||
|
|
||||||
"settingsCollectionTile": "Collezione",
|
"settingsCollectionTile": "Collezione",
|
||||||
|
|
||||||
|
@ -619,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Paesi più frequenti",
|
"statsTopCountriesSectionTitle": "Paesi più frequenti",
|
||||||
"statsTopPlacesSectionTitle": "Luoghi più frequenti",
|
"statsTopPlacesSectionTitle": "Luoghi più frequenti",
|
||||||
"statsTopTagsSectionTitle": "Etichette più frequenti",
|
"statsTopTagsSectionTitle": "Etichette più frequenti",
|
||||||
|
"statsTopAlbumsSectionTitle": "Album più frequenti",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "APRI PANORAMA",
|
"viewerOpenPanoramaButtonLabel": "APRI PANORAMA",
|
||||||
"viewerSetWallpaperButtonLabel": "IMPOSTA SFONDO",
|
"viewerSetWallpaperButtonLabel": "IMPOSTA SFONDO",
|
||||||
|
@ -663,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Risoluzione",
|
"viewerInfoSearchSuggestionResolution": "Risoluzione",
|
||||||
"viewerInfoSearchSuggestionRights": "Diritti",
|
"viewerInfoSearchSuggestionRights": "Diritti",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Usa effetto di scorrimento nella schermata iniziale",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Modifica etichette",
|
"tagEditorPageTitle": "Modifica etichette",
|
||||||
"tagEditorPageNewTagFieldLabel": "Nuova etichetta",
|
"tagEditorPageNewTagFieldLabel": "Nuova etichetta",
|
||||||
"tagEditorPageAddTagTooltip": "Aggiungi etichetta",
|
"tagEditorPageAddTagTooltip": "Aggiungi etichetta",
|
||||||
|
|
|
@ -151,9 +151,9 @@
|
||||||
"displayRefreshRatePreferHighest": "高レート",
|
"displayRefreshRatePreferHighest": "高レート",
|
||||||
"displayRefreshRatePreferLowest": "低レート",
|
"displayRefreshRatePreferLowest": "低レート",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "スキップ",
|
"videoPlaybackSkip": "スキップ",
|
||||||
"slideshowVideoPlaybackMuted": "ミュート再生",
|
"videoPlaybackMuted": "ミュート再生",
|
||||||
"slideshowVideoPlaybackWithSound": "音声あり再生",
|
"videoPlaybackWithSound": "音声あり再生",
|
||||||
|
|
||||||
"themeBrightnessLight": "ライト",
|
"themeBrightnessLight": "ライト",
|
||||||
"themeBrightnessDark": "ダーク",
|
"themeBrightnessDark": "ダーク",
|
||||||
|
@ -508,9 +508,7 @@
|
||||||
"settingsSlideshowShuffle": "シャッフル",
|
"settingsSlideshowShuffle": "シャッフル",
|
||||||
"settingsSlideshowFillScreen": "画面いっぱいに表示",
|
"settingsSlideshowFillScreen": "画面いっぱいに表示",
|
||||||
"settingsSlideshowTransitionTile": "トランジション",
|
"settingsSlideshowTransitionTile": "トランジション",
|
||||||
"settingsSlideshowTransitionDialogTitle": "トランジション",
|
|
||||||
"settingsSlideshowIntervalTile": "間隔",
|
"settingsSlideshowIntervalTile": "間隔",
|
||||||
"settingsSlideshowIntervalDialogTitle": "間隔",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "動画を再生",
|
"settingsSlideshowVideoPlaybackTile": "動画を再生",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "動画再生",
|
"settingsSlideshowVideoPlaybackDialogTitle": "動画再生",
|
||||||
|
|
||||||
|
@ -518,7 +516,7 @@
|
||||||
"settingsVideoSectionTitle": "動画",
|
"settingsVideoSectionTitle": "動画",
|
||||||
"settingsVideoShowVideos": "動画を表示",
|
"settingsVideoShowVideos": "動画を表示",
|
||||||
"settingsVideoEnableHardwareAcceleration": "ハードウェア アクセラレーション",
|
"settingsVideoEnableHardwareAcceleration": "ハードウェア アクセラレーション",
|
||||||
"settingsVideoEnableAutoPlay": "自動再生",
|
"settingsVideoAutoPlay": "自動再生",
|
||||||
"settingsVideoLoopModeTile": "ループ モード",
|
"settingsVideoLoopModeTile": "ループ モード",
|
||||||
"settingsVideoLoopModeDialogTitle": "ループ モード",
|
"settingsVideoLoopModeDialogTitle": "ループ モード",
|
||||||
|
|
||||||
|
@ -540,7 +538,6 @@
|
||||||
"settingsVideoControlsTile": "操作",
|
"settingsVideoControlsTile": "操作",
|
||||||
"settingsVideoControlsPageTitle": "操作",
|
"settingsVideoControlsPageTitle": "操作",
|
||||||
"settingsVideoButtonsTile": "ボタン",
|
"settingsVideoButtonsTile": "ボタン",
|
||||||
"settingsVideoButtonsDialogTitle": "ボタン",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "2回タップして再生/一時停止",
|
"settingsVideoGestureDoubleTapTogglePlay": "2回タップして再生/一時停止",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "画面の角を2回タップして早送り/早戻し",
|
"settingsVideoGestureSideDoubleTapSeek": "画面の角を2回タップして早送り/早戻し",
|
||||||
|
|
||||||
|
@ -573,7 +570,6 @@
|
||||||
"settingsRemoveAnimationsTile": "アニメーションの削除",
|
"settingsRemoveAnimationsTile": "アニメーションの削除",
|
||||||
"settingsRemoveAnimationsDialogTitle": "アニメーションの削除",
|
"settingsRemoveAnimationsDialogTitle": "アニメーションの削除",
|
||||||
"settingsTimeToTakeActionTile": "操作までの時間",
|
"settingsTimeToTakeActionTile": "操作までの時間",
|
||||||
"settingsTimeToTakeActionDialogTitle": "操作までの時間",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "ディスプレイ",
|
"settingsDisplaySectionTitle": "ディスプレイ",
|
||||||
"settingsThemeBrightnessTile": "テーマ",
|
"settingsThemeBrightnessTile": "テーマ",
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "앨범 페이지에서 보기",
|
"chipActionGoToAlbumPage": "앨범 페이지에서 보기",
|
||||||
"chipActionGoToCountryPage": "국가 페이지에서 보기",
|
"chipActionGoToCountryPage": "국가 페이지에서 보기",
|
||||||
"chipActionGoToTagPage": "태그 페이지에서 보기",
|
"chipActionGoToTagPage": "태그 페이지에서 보기",
|
||||||
|
"chipActionFilterOut": "제외하기",
|
||||||
|
"chipActionFilterIn": "포함시키기",
|
||||||
"chipActionHide": "숨기기",
|
"chipActionHide": "숨기기",
|
||||||
"chipActionPin": "고정",
|
"chipActionPin": "고정",
|
||||||
"chipActionUnpin": "고정 해제",
|
"chipActionUnpin": "고정 해제",
|
||||||
|
@ -155,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "가장 높은 재생률",
|
"displayRefreshRatePreferHighest": "가장 높은 재생률",
|
||||||
"displayRefreshRatePreferLowest": "가장 낮은 재생률",
|
"displayRefreshRatePreferLowest": "가장 낮은 재생률",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "생략",
|
"videoPlaybackSkip": "생략",
|
||||||
"slideshowVideoPlaybackMuted": "음소거 재생",
|
"videoPlaybackMuted": "음소거 재생",
|
||||||
"slideshowVideoPlaybackWithSound": "일반 재생",
|
"videoPlaybackWithSound": "일반 재생",
|
||||||
|
|
||||||
"themeBrightnessLight": "라이트",
|
"themeBrightnessLight": "라이트",
|
||||||
"themeBrightnessDark": "다크",
|
"themeBrightnessDark": "다크",
|
||||||
|
@ -167,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "시차",
|
"viewerTransitionParallax": "시차",
|
||||||
"viewerTransitionFade": "페이드",
|
"viewerTransitionFade": "페이드",
|
||||||
"viewerTransitionZoomIn": "확대",
|
"viewerTransitionZoomIn": "확대",
|
||||||
|
"viewerTransitionNone": "없음",
|
||||||
|
|
||||||
"wallpaperTargetHome": "홈 화면",
|
"wallpaperTargetHome": "홈 화면",
|
||||||
"wallpaperTargetLock": "잠금화면",
|
"wallpaperTargetLock": "잠금화면",
|
||||||
"wallpaperTargetHomeLock": "홈 및 잠금화면",
|
"wallpaperTargetHomeLock": "홈 및 잠금화면",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "홈 열기",
|
||||||
|
"widgetOpenPageViewer": "뷰어 열기",
|
||||||
|
|
||||||
"albumTierNew": "신규",
|
"albumTierNew": "신규",
|
||||||
"albumTierPinned": "고정",
|
"albumTierPinned": "고정",
|
||||||
"albumTierSpecial": "기본",
|
"albumTierSpecial": "기본",
|
||||||
|
@ -289,6 +295,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "배치",
|
"viewDialogLayoutSectionTitle": "배치",
|
||||||
"viewDialogReverseSortOrder": "순서를 뒤바꾸기",
|
"viewDialogReverseSortOrder": "순서를 뒤바꾸기",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "모자이크",
|
||||||
"tileLayoutGrid": "바둑판",
|
"tileLayoutGrid": "바둑판",
|
||||||
"tileLayoutList": "목록",
|
"tileLayoutList": "목록",
|
||||||
|
|
||||||
|
@ -401,9 +408,12 @@
|
||||||
"sortOrderSmallestFirst": "작은 파일순",
|
"sortOrderSmallestFirst": "작은 파일순",
|
||||||
|
|
||||||
"albumGroupTier": "단계별로",
|
"albumGroupTier": "단계별로",
|
||||||
|
"albumGroupType": "유형별로",
|
||||||
"albumGroupVolume": "저장공간별로",
|
"albumGroupVolume": "저장공간별로",
|
||||||
"albumGroupNone": "묶음 없음",
|
"albumGroupNone": "묶음 없음",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "혼합",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "앨범으로 복사",
|
"albumPickPageTitleCopy": "앨범으로 복사",
|
||||||
"albumPickPageTitleExport": "앨범으로 내보내기",
|
"albumPickPageTitleExport": "앨범으로 내보내기",
|
||||||
"albumPickPageTitleMove": "앨범으로 이동",
|
"albumPickPageTitleMove": "앨범으로 이동",
|
||||||
|
@ -440,8 +450,9 @@
|
||||||
"searchMetadataSectionTitle": "메타데이터",
|
"searchMetadataSectionTitle": "메타데이터",
|
||||||
|
|
||||||
"settingsPageTitle": "설정",
|
"settingsPageTitle": "설정",
|
||||||
"settingsSystemDefault": "시스템",
|
"settingsSystemDefault": "시스템 기본값",
|
||||||
"settingsDefault": "기본",
|
"settingsDefault": "기본",
|
||||||
|
"settingsDisabled": "사용 안함",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "설정 검색",
|
"settingsSearchFieldLabel": "설정 검색",
|
||||||
"settingsSearchEmpty": "결과가 없습니다",
|
"settingsSearchEmpty": "결과가 없습니다",
|
||||||
|
@ -524,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "반복",
|
"settingsSlideshowRepeat": "반복",
|
||||||
"settingsSlideshowShuffle": "순서섞기",
|
"settingsSlideshowShuffle": "순서섞기",
|
||||||
"settingsSlideshowFillScreen": "화면 채우기",
|
"settingsSlideshowFillScreen": "화면 채우기",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "애니메이션 확대/축소 효과",
|
||||||
"settingsSlideshowTransitionTile": "전환 효과",
|
"settingsSlideshowTransitionTile": "전환 효과",
|
||||||
"settingsSlideshowTransitionDialogTitle": "전환 효과",
|
|
||||||
"settingsSlideshowIntervalTile": "교체 주기",
|
"settingsSlideshowIntervalTile": "교체 주기",
|
||||||
"settingsSlideshowIntervalDialogTitle": "교체 주기",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "동영상 재생",
|
"settingsSlideshowVideoPlaybackTile": "동영상 재생",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "동영상 재생",
|
"settingsSlideshowVideoPlaybackDialogTitle": "동영상 재생",
|
||||||
|
|
||||||
|
@ -535,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "동영상",
|
"settingsVideoSectionTitle": "동영상",
|
||||||
"settingsVideoShowVideos": "미디어에 동영상 표시",
|
"settingsVideoShowVideos": "미디어에 동영상 표시",
|
||||||
"settingsVideoEnableHardwareAcceleration": "하드웨어 가속",
|
"settingsVideoEnableHardwareAcceleration": "하드웨어 가속",
|
||||||
"settingsVideoEnableAutoPlay": "자동 재생",
|
"settingsVideoAutoPlay": "자동 재생",
|
||||||
"settingsVideoLoopModeTile": "반복 모드",
|
"settingsVideoLoopModeTile": "반복 모드",
|
||||||
"settingsVideoLoopModeDialogTitle": "반복 모드",
|
"settingsVideoLoopModeDialogTitle": "반복 모드",
|
||||||
|
|
||||||
|
@ -557,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "제어",
|
"settingsVideoControlsTile": "제어",
|
||||||
"settingsVideoControlsPageTitle": "제어",
|
"settingsVideoControlsPageTitle": "제어",
|
||||||
"settingsVideoButtonsTile": "버튼",
|
"settingsVideoButtonsTile": "버튼",
|
||||||
"settingsVideoButtonsDialogTitle": "버튼",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "두 번 탭해서 재생이나 일시정지하기",
|
"settingsVideoGestureDoubleTapTogglePlay": "두 번 탭해서 재생이나 일시정지하기",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "화면 측면에서 두 번 탭해서 앞뒤로 가기",
|
"settingsVideoGestureSideDoubleTapSeek": "화면 측면에서 두 번 탭해서 앞뒤로 가기",
|
||||||
|
|
||||||
|
@ -590,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "애니메이션 삭제",
|
"settingsRemoveAnimationsTile": "애니메이션 삭제",
|
||||||
"settingsRemoveAnimationsDialogTitle": "애니메이션 삭제",
|
"settingsRemoveAnimationsDialogTitle": "애니메이션 삭제",
|
||||||
"settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간",
|
"settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간",
|
||||||
"settingsTimeToTakeActionDialogTitle": "액션 취하기 전 대기 시간",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "디스플레이",
|
"settingsDisplaySectionTitle": "디스플레이",
|
||||||
"settingsThemeBrightnessTile": "테마",
|
"settingsThemeBrightnessTile": "테마",
|
||||||
|
@ -612,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "사진 액자",
|
"settingsWidgetPageTitle": "사진 액자",
|
||||||
"settingsWidgetShowOutline": "윤곽",
|
"settingsWidgetShowOutline": "윤곽",
|
||||||
|
"settingsWidgetOpenPage": "위젯을 탭하면",
|
||||||
|
|
||||||
"settingsCollectionTile": "미디어",
|
"settingsCollectionTile": "미디어",
|
||||||
|
|
||||||
|
@ -620,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "국가 랭킹",
|
"statsTopCountriesSectionTitle": "국가 랭킹",
|
||||||
"statsTopPlacesSectionTitle": "장소 랭킹",
|
"statsTopPlacesSectionTitle": "장소 랭킹",
|
||||||
"statsTopTagsSectionTitle": "태그 랭킹",
|
"statsTopTagsSectionTitle": "태그 랭킹",
|
||||||
|
"statsTopAlbumsSectionTitle": "앨범 랭킹",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "파노라마 열기",
|
"viewerOpenPanoramaButtonLabel": "파노라마 열기",
|
||||||
"viewerSetWallpaperButtonLabel": "설정",
|
"viewerSetWallpaperButtonLabel": "설정",
|
||||||
|
@ -664,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "해상도",
|
"viewerInfoSearchSuggestionResolution": "해상도",
|
||||||
"viewerInfoSearchSuggestionRights": "권리",
|
"viewerInfoSearchSuggestionRights": "권리",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "홈 화면에 스크롤 효과 사용",
|
||||||
|
|
||||||
"tagEditorPageTitle": "태그 수정",
|
"tagEditorPageTitle": "태그 수정",
|
||||||
"tagEditorPageNewTagFieldLabel": "새 태그",
|
"tagEditorPageNewTagFieldLabel": "새 태그",
|
||||||
"tagEditorPageAddTagTooltip": "태그 추가",
|
"tagEditorPageAddTagTooltip": "태그 추가",
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "Tonen Albums",
|
"chipActionGoToAlbumPage": "Tonen Albums",
|
||||||
"chipActionGoToCountryPage": "Tonen in Landen",
|
"chipActionGoToCountryPage": "Tonen in Landen",
|
||||||
"chipActionGoToTagPage": "Tonen in Labels",
|
"chipActionGoToTagPage": "Tonen in Labels",
|
||||||
|
"chipActionFilterOut": "Uitfilteren",
|
||||||
|
"chipActionFilterIn": "Infilteren",
|
||||||
"chipActionHide": "Verbergen",
|
"chipActionHide": "Verbergen",
|
||||||
"chipActionPin": "Bovenaan pinnen",
|
"chipActionPin": "Bovenaan pinnen",
|
||||||
"chipActionUnpin": "Unpinnen",
|
"chipActionUnpin": "Unpinnen",
|
||||||
|
@ -155,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Hoogste waardering",
|
"displayRefreshRatePreferHighest": "Hoogste waardering",
|
||||||
"displayRefreshRatePreferLowest": "Laagste waardering",
|
"displayRefreshRatePreferLowest": "Laagste waardering",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Overslaan",
|
"videoPlaybackSkip": "Overslaan",
|
||||||
"slideshowVideoPlaybackMuted": "Gedempte afspelen",
|
"videoPlaybackMuted": "Gedempte afspelen",
|
||||||
"slideshowVideoPlaybackWithSound": "Met geluid afspelen",
|
"videoPlaybackWithSound": "Met geluid afspelen",
|
||||||
|
|
||||||
"themeBrightnessLight": "Licht",
|
"themeBrightnessLight": "Licht",
|
||||||
"themeBrightnessDark": "Donker",
|
"themeBrightnessDark": "Donker",
|
||||||
|
@ -167,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "Parallax",
|
"viewerTransitionParallax": "Parallax",
|
||||||
"viewerTransitionFade": "Vervagen",
|
"viewerTransitionFade": "Vervagen",
|
||||||
"viewerTransitionZoomIn": "Inzoomen",
|
"viewerTransitionZoomIn": "Inzoomen",
|
||||||
|
"viewerTransitionNone": "Geen",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Home scherm",
|
"wallpaperTargetHome": "Home scherm",
|
||||||
"wallpaperTargetLock": "Vergrendel scherm",
|
"wallpaperTargetLock": "Vergrendel scherm",
|
||||||
"wallpaperTargetHomeLock": "Home and Vergrendel schermen",
|
"wallpaperTargetHomeLock": "Home and Vergrendel schermen",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Open startscherm",
|
||||||
|
"widgetOpenPageViewer": "Open viewer",
|
||||||
|
|
||||||
"albumTierNew": "Nieuw",
|
"albumTierNew": "Nieuw",
|
||||||
"albumTierPinned": "Gepint",
|
"albumTierPinned": "Gepint",
|
||||||
"albumTierSpecial": "Veelgebruikt",
|
"albumTierSpecial": "Veelgebruikt",
|
||||||
|
@ -289,6 +295,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "Layout",
|
"viewDialogLayoutSectionTitle": "Layout",
|
||||||
"viewDialogReverseSortOrder": "Draai sorteerrichting om",
|
"viewDialogReverseSortOrder": "Draai sorteerrichting om",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Mozaïek",
|
||||||
"tileLayoutGrid": "Raster",
|
"tileLayoutGrid": "Raster",
|
||||||
"tileLayoutList": "Lijst",
|
"tileLayoutList": "Lijst",
|
||||||
|
|
||||||
|
@ -401,9 +408,12 @@
|
||||||
"sortOrderSmallestFirst": "Kleinste eerst",
|
"sortOrderSmallestFirst": "Kleinste eerst",
|
||||||
|
|
||||||
"albumGroupTier": "Op rang",
|
"albumGroupTier": "Op rang",
|
||||||
|
"albumGroupType": "Op type",
|
||||||
"albumGroupVolume": "Op opslagvolume",
|
"albumGroupVolume": "Op opslagvolume",
|
||||||
"albumGroupNone": "Niet groeperen",
|
"albumGroupNone": "Niet groeperen",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Gemengd",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "Kopieer naar Album",
|
"albumPickPageTitleCopy": "Kopieer naar Album",
|
||||||
"albumPickPageTitleExport": "Exporteer naar Album",
|
"albumPickPageTitleExport": "Exporteer naar Album",
|
||||||
"albumPickPageTitleMove": "Verplaats naar Album",
|
"albumPickPageTitleMove": "Verplaats naar Album",
|
||||||
|
@ -442,6 +452,7 @@
|
||||||
"settingsPageTitle": "Instellingen",
|
"settingsPageTitle": "Instellingen",
|
||||||
"settingsSystemDefault": "Systeem",
|
"settingsSystemDefault": "Systeem",
|
||||||
"settingsDefault": "Standaard",
|
"settingsDefault": "Standaard",
|
||||||
|
"settingsDisabled": "Uitgeschakeld",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Instellingen doorzoeken",
|
"settingsSearchFieldLabel": "Instellingen doorzoeken",
|
||||||
"settingsSearchEmpty": "Geen instellingen gevonden",
|
"settingsSearchEmpty": "Geen instellingen gevonden",
|
||||||
|
@ -524,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "Herhalen",
|
"settingsSlideshowRepeat": "Herhalen",
|
||||||
"settingsSlideshowShuffle": "Shuffle",
|
"settingsSlideshowShuffle": "Shuffle",
|
||||||
"settingsSlideshowFillScreen": "Volledig scherm",
|
"settingsSlideshowFillScreen": "Volledig scherm",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Geanimeerd zoomeffect",
|
||||||
"settingsSlideshowTransitionTile": "Overgang",
|
"settingsSlideshowTransitionTile": "Overgang",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Overgang",
|
|
||||||
"settingsSlideshowIntervalTile": "Interval",
|
"settingsSlideshowIntervalTile": "Interval",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Interval",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Video afspelen",
|
"settingsSlideshowVideoPlaybackTile": "Video afspelen",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Video afspelen",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Video afspelen",
|
||||||
|
|
||||||
|
@ -535,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "Video",
|
"settingsVideoSectionTitle": "Video",
|
||||||
"settingsVideoShowVideos": "Videos",
|
"settingsVideoShowVideos": "Videos",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Hardware acceleratie",
|
"settingsVideoEnableHardwareAcceleration": "Hardware acceleratie",
|
||||||
"settingsVideoEnableAutoPlay": "Automatisch afspelen",
|
"settingsVideoAutoPlay": "Automatisch afspelen",
|
||||||
"settingsVideoLoopModeTile": "Herhaald afspelen",
|
"settingsVideoLoopModeTile": "Herhaald afspelen",
|
||||||
"settingsVideoLoopModeDialogTitle": "Herhaald afspelen",
|
"settingsVideoLoopModeDialogTitle": "Herhaald afspelen",
|
||||||
|
|
||||||
|
@ -557,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "Bediening",
|
"settingsVideoControlsTile": "Bediening",
|
||||||
"settingsVideoControlsPageTitle": "Bediening",
|
"settingsVideoControlsPageTitle": "Bediening",
|
||||||
"settingsVideoButtonsTile": "Knoppen",
|
"settingsVideoButtonsTile": "Knoppen",
|
||||||
"settingsVideoButtonsDialogTitle": "Knoppen",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Dubbeltik om te spelen/pauzeren",
|
"settingsVideoGestureDoubleTapTogglePlay": "Dubbeltik om te spelen/pauzeren",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Dubbeltik op schermranden om achteruit/vooruit te zoeken",
|
"settingsVideoGestureSideDoubleTapSeek": "Dubbeltik op schermranden om achteruit/vooruit te zoeken",
|
||||||
|
|
||||||
|
@ -590,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Animaties verwijderen",
|
"settingsRemoveAnimationsTile": "Animaties verwijderen",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Animaties verwijderen",
|
"settingsRemoveAnimationsDialogTitle": "Animaties verwijderen",
|
||||||
"settingsTimeToTakeActionTile": "Tijd om actie te ondernemen",
|
"settingsTimeToTakeActionTile": "Tijd om actie te ondernemen",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Tijd om actie te ondernemen",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Scherm",
|
"settingsDisplaySectionTitle": "Scherm",
|
||||||
"settingsThemeBrightnessTile": "Thema",
|
"settingsThemeBrightnessTile": "Thema",
|
||||||
|
@ -612,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Foto Lijstje",
|
"settingsWidgetPageTitle": "Foto Lijstje",
|
||||||
"settingsWidgetShowOutline": "Contour",
|
"settingsWidgetShowOutline": "Contour",
|
||||||
|
"settingsWidgetOpenPage": "Wanneer u op de widget tikt",
|
||||||
|
|
||||||
"settingsCollectionTile": "Verzameling",
|
"settingsCollectionTile": "Verzameling",
|
||||||
|
|
||||||
|
@ -620,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Top Landen",
|
"statsTopCountriesSectionTitle": "Top Landen",
|
||||||
"statsTopPlacesSectionTitle": "Top Plaatsen",
|
"statsTopPlacesSectionTitle": "Top Plaatsen",
|
||||||
"statsTopTagsSectionTitle": "Top Labels",
|
"statsTopTagsSectionTitle": "Top Labels",
|
||||||
|
"statsTopAlbumsSectionTitle": "Top Albums",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "OPEN PANORAMA",
|
"viewerOpenPanoramaButtonLabel": "OPEN PANORAMA",
|
||||||
"viewerSetWallpaperButtonLabel": "ALS ACHTERGROND INSTELLEN",
|
"viewerSetWallpaperButtonLabel": "ALS ACHTERGROND INSTELLEN",
|
||||||
|
@ -664,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Resolutie",
|
"viewerInfoSearchSuggestionResolution": "Resolutie",
|
||||||
"viewerInfoSearchSuggestionRights": "Rechten",
|
"viewerInfoSearchSuggestionRights": "Rechten",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Scroll-effect gebruiken op startscherm",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Wijzig Labels",
|
"tagEditorPageTitle": "Wijzig Labels",
|
||||||
"tagEditorPageNewTagFieldLabel": "Nieuw label",
|
"tagEditorPageNewTagFieldLabel": "Nieuw label",
|
||||||
"tagEditorPageAddTagTooltip": "Label toevoegen",
|
"tagEditorPageAddTagTooltip": "Label toevoegen",
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "Mostrar nos Álbuns",
|
"chipActionGoToAlbumPage": "Mostrar nos Álbuns",
|
||||||
"chipActionGoToCountryPage": "Mostrar em Países",
|
"chipActionGoToCountryPage": "Mostrar em Países",
|
||||||
"chipActionGoToTagPage": "Mostrar em Etiquetas",
|
"chipActionGoToTagPage": "Mostrar em Etiquetas",
|
||||||
|
"chipActionFilterOut": "Filtrar dentro",
|
||||||
|
"chipActionFilterIn": "Filtrar fora",
|
||||||
"chipActionHide": "Ocultar",
|
"chipActionHide": "Ocultar",
|
||||||
"chipActionPin": "Fixar no topo",
|
"chipActionPin": "Fixar no topo",
|
||||||
"chipActionUnpin": "Desafixar do topo",
|
"chipActionUnpin": "Desafixar do topo",
|
||||||
|
@ -88,15 +90,18 @@
|
||||||
|
|
||||||
"entryInfoActionEditDate": "Editar data e hora",
|
"entryInfoActionEditDate": "Editar data e hora",
|
||||||
"entryInfoActionEditLocation": "Editar localização",
|
"entryInfoActionEditLocation": "Editar localização",
|
||||||
|
"entryInfoActionEditTitleDescription": "Editar título e descrição",
|
||||||
"entryInfoActionEditRating": "Editar classificação",
|
"entryInfoActionEditRating": "Editar classificação",
|
||||||
"entryInfoActionEditTags": "Editar etiquetas",
|
"entryInfoActionEditTags": "Editar etiquetas",
|
||||||
"entryInfoActionRemoveMetadata": "Remover metadados",
|
"entryInfoActionRemoveMetadata": "Remover metadados",
|
||||||
|
|
||||||
"filterBinLabel": "Lixeira",
|
"filterBinLabel": "Lixeira",
|
||||||
"filterFavouriteLabel": "Favorito",
|
"filterFavouriteLabel": "Favorito",
|
||||||
|
"filterNoDateLabel": "Sem data",
|
||||||
"filterNoLocationLabel": "Não localizado",
|
"filterNoLocationLabel": "Não localizado",
|
||||||
"filterNoRatingLabel": "Sem classificação",
|
"filterNoRatingLabel": "Sem classificação",
|
||||||
"filterNoTagLabel": "Sem etiqueta",
|
"filterNoTagLabel": "Sem etiqueta",
|
||||||
|
"filterNoTitleLabel": "Sem título",
|
||||||
"filterOnThisDayLabel": "Neste dia",
|
"filterOnThisDayLabel": "Neste dia",
|
||||||
"filterRecentlyAddedLabel": "Adicionado recentemente",
|
"filterRecentlyAddedLabel": "Adicionado recentemente",
|
||||||
"filterRatingRejectedLabel": "Rejeitado",
|
"filterRatingRejectedLabel": "Rejeitado",
|
||||||
|
@ -152,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Taxa mais alta",
|
"displayRefreshRatePreferHighest": "Taxa mais alta",
|
||||||
"displayRefreshRatePreferLowest": "Taxa mais baixa",
|
"displayRefreshRatePreferLowest": "Taxa mais baixa",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Pular",
|
"videoPlaybackSkip": "Pular",
|
||||||
"slideshowVideoPlaybackMuted": "Reproduzir sem som",
|
"videoPlaybackMuted": "Reproduzir sem som",
|
||||||
"slideshowVideoPlaybackWithSound": "Reproduzir com som",
|
"videoPlaybackWithSound": "Reproduzir com som",
|
||||||
|
|
||||||
"themeBrightnessLight": "Claro",
|
"themeBrightnessLight": "Claro",
|
||||||
"themeBrightnessDark": "Escuro",
|
"themeBrightnessDark": "Escuro",
|
||||||
|
@ -164,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "Parallax",
|
"viewerTransitionParallax": "Parallax",
|
||||||
"viewerTransitionFade": "Desvaneça",
|
"viewerTransitionFade": "Desvaneça",
|
||||||
"viewerTransitionZoomIn": "Mais zoom",
|
"viewerTransitionZoomIn": "Mais zoom",
|
||||||
|
"viewerTransitionNone": "Nenhum",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Tela inicial",
|
"wallpaperTargetHome": "Tela inicial",
|
||||||
"wallpaperTargetLock": "Tela de bloqueio",
|
"wallpaperTargetLock": "Tela de bloqueio",
|
||||||
"wallpaperTargetHomeLock": "Telas iniciais e de bloqueio",
|
"wallpaperTargetHomeLock": "Telas iniciais e de bloqueio",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Abrir inicial",
|
||||||
|
"widgetOpenPageViewer": "Abrir visualizador",
|
||||||
|
|
||||||
"albumTierNew": "Novo",
|
"albumTierNew": "Novo",
|
||||||
"albumTierPinned": "Fixada",
|
"albumTierPinned": "Fixada",
|
||||||
"albumTierSpecial": "Comum",
|
"albumTierSpecial": "Comum",
|
||||||
|
@ -284,7 +293,9 @@
|
||||||
"viewDialogSortSectionTitle": "Organizar",
|
"viewDialogSortSectionTitle": "Organizar",
|
||||||
"viewDialogGroupSectionTitle": "Grupo",
|
"viewDialogGroupSectionTitle": "Grupo",
|
||||||
"viewDialogLayoutSectionTitle": "Layout",
|
"viewDialogLayoutSectionTitle": "Layout",
|
||||||
|
"viewDialogReverseSortOrder": "Ordem de classificação inversa",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Mosaico",
|
||||||
"tileLayoutGrid": "Grid",
|
"tileLayoutGrid": "Grid",
|
||||||
"tileLayoutList": "Lista",
|
"tileLayoutList": "Lista",
|
||||||
|
|
||||||
|
@ -387,10 +398,22 @@
|
||||||
"sortByAlbumFileName": "Por álbum e nome de arquivo",
|
"sortByAlbumFileName": "Por álbum e nome de arquivo",
|
||||||
"sortByRating": "Por classificação",
|
"sortByRating": "Por classificação",
|
||||||
|
|
||||||
|
"sortOrderNewestFirst": "Os mais novos primeiro",
|
||||||
|
"sortOrderOldestFirst": "Mais velhos primeiro",
|
||||||
|
"sortOrderAtoZ": "A a Z",
|
||||||
|
"sortOrderZtoA": "Z a A",
|
||||||
|
"sortOrderHighestFirst": "Mais alto primeiro",
|
||||||
|
"sortOrderLowestFirst": "Mais baixo primeiro",
|
||||||
|
"sortOrderLargestFirst": "Maior primeiro",
|
||||||
|
"sortOrderSmallestFirst": "Menor primeiro",
|
||||||
|
|
||||||
|
"albumGroupType": "Por tipo",
|
||||||
"albumGroupTier": "Por nível",
|
"albumGroupTier": "Por nível",
|
||||||
"albumGroupVolume": "Por volume de armazenamento",
|
"albumGroupVolume": "Por volume de armazenamento",
|
||||||
"albumGroupNone": "Não agrupe",
|
"albumGroupNone": "Não agrupe",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Misturado",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "Copiar para o álbum",
|
"albumPickPageTitleCopy": "Copiar para o álbum",
|
||||||
"albumPickPageTitleExport": "Exportar para o álbum",
|
"albumPickPageTitleExport": "Exportar para o álbum",
|
||||||
"albumPickPageTitleMove": "Mover para o álbum",
|
"albumPickPageTitleMove": "Mover para o álbum",
|
||||||
|
@ -424,10 +447,12 @@
|
||||||
"searchPlacesSectionTitle": "Locais",
|
"searchPlacesSectionTitle": "Locais",
|
||||||
"searchTagsSectionTitle": "Etiquetas",
|
"searchTagsSectionTitle": "Etiquetas",
|
||||||
"searchRatingSectionTitle": "Classificações",
|
"searchRatingSectionTitle": "Classificações",
|
||||||
|
"searchMetadataSectionTitle": "Metadados",
|
||||||
|
|
||||||
"settingsPageTitle": "Configurações",
|
"settingsPageTitle": "Configurações",
|
||||||
"settingsSystemDefault": "Sistema",
|
"settingsSystemDefault": "Sistema",
|
||||||
"settingsDefault": "Padrão",
|
"settingsDefault": "Padrão",
|
||||||
|
"settingsDisabled": "Desativado",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Pesquisar configuração",
|
"settingsSearchFieldLabel": "Pesquisar configuração",
|
||||||
"settingsSearchEmpty": "Nenhuma configuração correspondente",
|
"settingsSearchEmpty": "Nenhuma configuração correspondente",
|
||||||
|
@ -510,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "Repetir",
|
"settingsSlideshowRepeat": "Repetir",
|
||||||
"settingsSlideshowShuffle": "Embaralhar",
|
"settingsSlideshowShuffle": "Embaralhar",
|
||||||
"settingsSlideshowFillScreen": "Preencher tela",
|
"settingsSlideshowFillScreen": "Preencher tela",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Efeito de zoom animado",
|
||||||
"settingsSlideshowTransitionTile": "Transição",
|
"settingsSlideshowTransitionTile": "Transição",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Transição",
|
|
||||||
"settingsSlideshowIntervalTile": "Intervalo",
|
"settingsSlideshowIntervalTile": "Intervalo",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Intervalo",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Reprodução de vídeo",
|
"settingsSlideshowVideoPlaybackTile": "Reprodução de vídeo",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Reprodução de vídeo",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Reprodução de vídeo",
|
||||||
|
|
||||||
|
@ -521,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "Vídeo",
|
"settingsVideoSectionTitle": "Vídeo",
|
||||||
"settingsVideoShowVideos": "Mostrar vídeos",
|
"settingsVideoShowVideos": "Mostrar vídeos",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Aceleraçao do hardware",
|
"settingsVideoEnableHardwareAcceleration": "Aceleraçao do hardware",
|
||||||
"settingsVideoEnableAutoPlay": "Reprodução automática",
|
"settingsVideoAutoPlay": "Reprodução automática",
|
||||||
"settingsVideoLoopModeTile": "Modo de loop",
|
"settingsVideoLoopModeTile": "Modo de loop",
|
||||||
"settingsVideoLoopModeDialogTitle": "Modo de loop",
|
"settingsVideoLoopModeDialogTitle": "Modo de loop",
|
||||||
|
|
||||||
|
@ -543,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "Controles",
|
"settingsVideoControlsTile": "Controles",
|
||||||
"settingsVideoControlsPageTitle": "Controles",
|
"settingsVideoControlsPageTitle": "Controles",
|
||||||
"settingsVideoButtonsTile": "Botões",
|
"settingsVideoButtonsTile": "Botões",
|
||||||
"settingsVideoButtonsDialogTitle": "Botões",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Toque duas vezes para reproduzir/pausar",
|
"settingsVideoGestureDoubleTapTogglePlay": "Toque duas vezes para reproduzir/pausar",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Toque duas vezes nas bordas da tela buscar para trás/para frente",
|
"settingsVideoGestureSideDoubleTapSeek": "Toque duas vezes nas bordas da tela buscar para trás/para frente",
|
||||||
|
|
||||||
|
@ -576,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Remover animações",
|
"settingsRemoveAnimationsTile": "Remover animações",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Remover Animações",
|
"settingsRemoveAnimationsDialogTitle": "Remover Animações",
|
||||||
"settingsTimeToTakeActionTile": "Tempo para executar uma ação",
|
"settingsTimeToTakeActionTile": "Tempo para executar uma ação",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Tempo para executar uma ação",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Tela",
|
"settingsDisplaySectionTitle": "Tela",
|
||||||
"settingsThemeBrightnessTile": "Tema",
|
"settingsThemeBrightnessTile": "Tema",
|
||||||
|
@ -598,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Porta-retratos",
|
"settingsWidgetPageTitle": "Porta-retratos",
|
||||||
"settingsWidgetShowOutline": "Contorno",
|
"settingsWidgetShowOutline": "Contorno",
|
||||||
|
"settingsWidgetOpenPage": "Ao tocar no widget",
|
||||||
|
|
||||||
"settingsCollectionTile": "Coleção",
|
"settingsCollectionTile": "Coleção",
|
||||||
|
|
||||||
|
@ -606,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Principais Países",
|
"statsTopCountriesSectionTitle": "Principais Países",
|
||||||
"statsTopPlacesSectionTitle": "Principais Lugares",
|
"statsTopPlacesSectionTitle": "Principais Lugares",
|
||||||
"statsTopTagsSectionTitle": "Principais Etiquetas",
|
"statsTopTagsSectionTitle": "Principais Etiquetas",
|
||||||
|
"statsTopAlbumsSectionTitle": "Principais Álbuns",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "ABRIR PANORAMA",
|
"viewerOpenPanoramaButtonLabel": "ABRIR PANORAMA",
|
||||||
"viewerSetWallpaperButtonLabel": "DEFINIR PAPEL DE PAREDE",
|
"viewerSetWallpaperButtonLabel": "DEFINIR PAPEL DE PAREDE",
|
||||||
|
@ -650,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Resolução",
|
"viewerInfoSearchSuggestionResolution": "Resolução",
|
||||||
"viewerInfoSearchSuggestionRights": "Direitos",
|
"viewerInfoSearchSuggestionRights": "Direitos",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Use o efeito de rolagem na tela inicial",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Editar etiquetas",
|
"tagEditorPageTitle": "Editar etiquetas",
|
||||||
"tagEditorPageNewTagFieldLabel": "Nova etiqueta",
|
"tagEditorPageNewTagFieldLabel": "Nova etiqueta",
|
||||||
"tagEditorPageAddTagTooltip": "Adicionar etiqueta",
|
"tagEditorPageAddTagTooltip": "Adicionar etiqueta",
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "Показать в Альбомах",
|
"chipActionGoToAlbumPage": "Показать в Альбомах",
|
||||||
"chipActionGoToCountryPage": "Показать в Странах",
|
"chipActionGoToCountryPage": "Показать в Странах",
|
||||||
"chipActionGoToTagPage": "Показать в тегах",
|
"chipActionGoToTagPage": "Показать в тегах",
|
||||||
|
"chipActionFilterOut": "Исключить",
|
||||||
|
"chipActionFilterIn": "Включить",
|
||||||
"chipActionHide": "Скрыть",
|
"chipActionHide": "Скрыть",
|
||||||
"chipActionPin": "Закрепить",
|
"chipActionPin": "Закрепить",
|
||||||
"chipActionUnpin": "Открепить",
|
"chipActionUnpin": "Открепить",
|
||||||
|
@ -155,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "Наивысшая частота",
|
"displayRefreshRatePreferHighest": "Наивысшая частота",
|
||||||
"displayRefreshRatePreferLowest": "Наименьшая частота",
|
"displayRefreshRatePreferLowest": "Наименьшая частота",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "Пропустить",
|
"videoPlaybackSkip": "Пропустить",
|
||||||
"slideshowVideoPlaybackMuted": "Играть без звука",
|
"videoPlaybackMuted": "Играть без звука",
|
||||||
"slideshowVideoPlaybackWithSound": "Играть со звуком",
|
"videoPlaybackWithSound": "Играть со звуком",
|
||||||
|
|
||||||
"themeBrightnessLight": "Светлая",
|
"themeBrightnessLight": "Светлая",
|
||||||
"themeBrightnessDark": "Тёмная",
|
"themeBrightnessDark": "Тёмная",
|
||||||
|
@ -167,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "Параллакс",
|
"viewerTransitionParallax": "Параллакс",
|
||||||
"viewerTransitionFade": "Затухание",
|
"viewerTransitionFade": "Затухание",
|
||||||
"viewerTransitionZoomIn": "Приближение",
|
"viewerTransitionZoomIn": "Приближение",
|
||||||
|
"viewerTransitionNone": "Нет",
|
||||||
|
|
||||||
"wallpaperTargetHome": "Домашний экран",
|
"wallpaperTargetHome": "Домашний экран",
|
||||||
"wallpaperTargetLock": "Экран блокировки",
|
"wallpaperTargetLock": "Экран блокировки",
|
||||||
"wallpaperTargetHomeLock": "Домашний экран и экран блокировки",
|
"wallpaperTargetHomeLock": "Домашний экран и экран блокировки",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "Открыть главную страницу",
|
||||||
|
"widgetOpenPageViewer": "Просмотр текущего",
|
||||||
|
|
||||||
"albumTierNew": "Новые",
|
"albumTierNew": "Новые",
|
||||||
"albumTierPinned": "Закрепленные",
|
"albumTierPinned": "Закрепленные",
|
||||||
"albumTierSpecial": "Стандартные",
|
"albumTierSpecial": "Стандартные",
|
||||||
|
@ -289,6 +295,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "Макет",
|
"viewDialogLayoutSectionTitle": "Макет",
|
||||||
"viewDialogReverseSortOrder": "Обратный порядок сортировки",
|
"viewDialogReverseSortOrder": "Обратный порядок сортировки",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "Мозайка",
|
||||||
"tileLayoutGrid": "Сетка",
|
"tileLayoutGrid": "Сетка",
|
||||||
"tileLayoutList": "Список",
|
"tileLayoutList": "Список",
|
||||||
|
|
||||||
|
@ -401,9 +408,12 @@
|
||||||
"sortOrderSmallestFirst": "Сначала маленькие",
|
"sortOrderSmallestFirst": "Сначала маленькие",
|
||||||
|
|
||||||
"albumGroupTier": "По уровню",
|
"albumGroupTier": "По уровню",
|
||||||
|
"albumGroupType": "По типу",
|
||||||
"albumGroupVolume": "По накопителю",
|
"albumGroupVolume": "По накопителю",
|
||||||
"albumGroupNone": "Не группировать",
|
"albumGroupNone": "Не группировать",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "Разное",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "Копировать в альбом",
|
"albumPickPageTitleCopy": "Копировать в альбом",
|
||||||
"albumPickPageTitleExport": "Экспорт в альбом",
|
"albumPickPageTitleExport": "Экспорт в альбом",
|
||||||
"albumPickPageTitleMove": "Переместить в альбом",
|
"albumPickPageTitleMove": "Переместить в альбом",
|
||||||
|
@ -442,6 +452,7 @@
|
||||||
"settingsPageTitle": "Настройки",
|
"settingsPageTitle": "Настройки",
|
||||||
"settingsSystemDefault": "Система",
|
"settingsSystemDefault": "Система",
|
||||||
"settingsDefault": "По умолчанию",
|
"settingsDefault": "По умолчанию",
|
||||||
|
"settingsDisabled": "Выключено",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Поиск настроек",
|
"settingsSearchFieldLabel": "Поиск настроек",
|
||||||
"settingsSearchEmpty": "Нет соответствующих настроек",
|
"settingsSearchEmpty": "Нет соответствующих настроек",
|
||||||
|
@ -524,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "Повтор",
|
"settingsSlideshowRepeat": "Повтор",
|
||||||
"settingsSlideshowShuffle": "Вперемешку",
|
"settingsSlideshowShuffle": "Вперемешку",
|
||||||
"settingsSlideshowFillScreen": "Полный экран",
|
"settingsSlideshowFillScreen": "Полный экран",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "Анимация зум эффекта",
|
||||||
"settingsSlideshowTransitionTile": "Эффект перехода",
|
"settingsSlideshowTransitionTile": "Эффект перехода",
|
||||||
"settingsSlideshowTransitionDialogTitle": "Эффект Перехода",
|
|
||||||
"settingsSlideshowIntervalTile": "Интервал",
|
"settingsSlideshowIntervalTile": "Интервал",
|
||||||
"settingsSlideshowIntervalDialogTitle": "Интервал",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "Проигрывание видео",
|
"settingsSlideshowVideoPlaybackTile": "Проигрывание видео",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Проигрывание Видео",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Проигрывание Видео",
|
||||||
|
|
||||||
|
@ -535,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "Видео",
|
"settingsVideoSectionTitle": "Видео",
|
||||||
"settingsVideoShowVideos": "Показать видео",
|
"settingsVideoShowVideos": "Показать видео",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Аппаратное ускорение",
|
"settingsVideoEnableHardwareAcceleration": "Аппаратное ускорение",
|
||||||
"settingsVideoEnableAutoPlay": "Автозапуск воспроизведения",
|
"settingsVideoAutoPlay": "Автозапуск воспроизведения",
|
||||||
"settingsVideoLoopModeTile": "Циклический режим",
|
"settingsVideoLoopModeTile": "Циклический режим",
|
||||||
"settingsVideoLoopModeDialogTitle": "Цикличный режим",
|
"settingsVideoLoopModeDialogTitle": "Цикличный режим",
|
||||||
|
|
||||||
|
@ -557,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "Элементы управления",
|
"settingsVideoControlsTile": "Элементы управления",
|
||||||
"settingsVideoControlsPageTitle": "Элементы управления",
|
"settingsVideoControlsPageTitle": "Элементы управления",
|
||||||
"settingsVideoButtonsTile": "Кнопки",
|
"settingsVideoButtonsTile": "Кнопки",
|
||||||
"settingsVideoButtonsDialogTitle": "Кнопки",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Двойное нажатие для воспроизведения/паузы",
|
"settingsVideoGestureDoubleTapTogglePlay": "Двойное нажатие для воспроизведения/паузы",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Двойное нажатие на края экрана для перехода назад/вперёд",
|
"settingsVideoGestureSideDoubleTapSeek": "Двойное нажатие на края экрана для перехода назад/вперёд",
|
||||||
|
|
||||||
|
@ -590,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Удалить анимацию",
|
"settingsRemoveAnimationsTile": "Удалить анимацию",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Удалить анимацию",
|
"settingsRemoveAnimationsDialogTitle": "Удалить анимацию",
|
||||||
"settingsTimeToTakeActionTile": "Время на выполнение действия",
|
"settingsTimeToTakeActionTile": "Время на выполнение действия",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Время на выполнение действия",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Отображение",
|
"settingsDisplaySectionTitle": "Отображение",
|
||||||
"settingsThemeBrightnessTile": "Тема",
|
"settingsThemeBrightnessTile": "Тема",
|
||||||
|
@ -612,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "Фоторамка",
|
"settingsWidgetPageTitle": "Фоторамка",
|
||||||
"settingsWidgetShowOutline": "Выделение",
|
"settingsWidgetShowOutline": "Выделение",
|
||||||
|
"settingsWidgetOpenPage": "При нажатии на виджет",
|
||||||
|
|
||||||
"settingsCollectionTile": "Коллекция",
|
"settingsCollectionTile": "Коллекция",
|
||||||
|
|
||||||
|
@ -620,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "Топ стран",
|
"statsTopCountriesSectionTitle": "Топ стран",
|
||||||
"statsTopPlacesSectionTitle": "Топ локаций",
|
"statsTopPlacesSectionTitle": "Топ локаций",
|
||||||
"statsTopTagsSectionTitle": "Топ тегов",
|
"statsTopTagsSectionTitle": "Топ тегов",
|
||||||
|
"statsTopAlbumsSectionTitle": "Топ альбомов",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "ОТКРЫТЬ ПАНОРАМУ",
|
"viewerOpenPanoramaButtonLabel": "ОТКРЫТЬ ПАНОРАМУ",
|
||||||
"viewerSetWallpaperButtonLabel": "УСТАНОВИТЬ КАК ОБОИ",
|
"viewerSetWallpaperButtonLabel": "УСТАНОВИТЬ КАК ОБОИ",
|
||||||
|
@ -664,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "Разрешение",
|
"viewerInfoSearchSuggestionResolution": "Разрешение",
|
||||||
"viewerInfoSearchSuggestionRights": "Права",
|
"viewerInfoSearchSuggestionRights": "Права",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "Эффект прокрутки на домашнем экране",
|
||||||
|
|
||||||
"tagEditorPageTitle": "Изменить теги",
|
"tagEditorPageTitle": "Изменить теги",
|
||||||
"tagEditorPageNewTagFieldLabel": "Новый тег",
|
"tagEditorPageNewTagFieldLabel": "Новый тег",
|
||||||
"tagEditorPageAddTagTooltip": "Добавить тег",
|
"tagEditorPageAddTagTooltip": "Добавить тег",
|
||||||
|
|
|
@ -502,7 +502,7 @@
|
||||||
"settingsVideoSectionTitle": "Video",
|
"settingsVideoSectionTitle": "Video",
|
||||||
"settingsVideoShowVideos": "Videoları göster",
|
"settingsVideoShowVideos": "Videoları göster",
|
||||||
"settingsVideoEnableHardwareAcceleration": "Donanım hızlandırma",
|
"settingsVideoEnableHardwareAcceleration": "Donanım hızlandırma",
|
||||||
"settingsVideoEnableAutoPlay": "Otomatik oynat",
|
"settingsVideoAutoPlay": "Otomatik oynat",
|
||||||
"settingsVideoLoopModeTile": "Döngü modu",
|
"settingsVideoLoopModeTile": "Döngü modu",
|
||||||
"settingsVideoLoopModeDialogTitle": "Döngü Modu",
|
"settingsVideoLoopModeDialogTitle": "Döngü Modu",
|
||||||
|
|
||||||
|
@ -524,7 +524,6 @@
|
||||||
"settingsVideoControlsTile": "Kontroller",
|
"settingsVideoControlsTile": "Kontroller",
|
||||||
"settingsVideoControlsPageTitle": "Kontroller",
|
"settingsVideoControlsPageTitle": "Kontroller",
|
||||||
"settingsVideoButtonsTile": "Düğmeler",
|
"settingsVideoButtonsTile": "Düğmeler",
|
||||||
"settingsVideoButtonsDialogTitle": "Düğmeler",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Oynatmak/duraklatmak için çift dokunun",
|
"settingsVideoGestureDoubleTapTogglePlay": "Oynatmak/duraklatmak için çift dokunun",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Geri/ileri aramak için ekran kenarlarına çift dokunun",
|
"settingsVideoGestureSideDoubleTapSeek": "Geri/ileri aramak için ekran kenarlarına çift dokunun",
|
||||||
|
|
||||||
|
@ -557,7 +556,6 @@
|
||||||
"settingsRemoveAnimationsTile": "Animasyonları kaldır",
|
"settingsRemoveAnimationsTile": "Animasyonları kaldır",
|
||||||
"settingsRemoveAnimationsDialogTitle": "Animasyonları Kaldır",
|
"settingsRemoveAnimationsDialogTitle": "Animasyonları Kaldır",
|
||||||
"settingsTimeToTakeActionTile": "Harekete geçme zamanı",
|
"settingsTimeToTakeActionTile": "Harekete geçme zamanı",
|
||||||
"settingsTimeToTakeActionDialogTitle": "Harekete Geçme Zamanı",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "Ekran",
|
"settingsDisplaySectionTitle": "Ekran",
|
||||||
"settingsThemeBrightnessTile": "Tema",
|
"settingsThemeBrightnessTile": "Tema",
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
"chipActionGoToAlbumPage": "在相册中显示",
|
"chipActionGoToAlbumPage": "在相册中显示",
|
||||||
"chipActionGoToCountryPage": "在国家中显示",
|
"chipActionGoToCountryPage": "在国家中显示",
|
||||||
"chipActionGoToTagPage": "在标签中显示",
|
"chipActionGoToTagPage": "在标签中显示",
|
||||||
|
"chipActionFilterOut": "滤除",
|
||||||
|
"chipActionFilterIn": "筛选",
|
||||||
"chipActionHide": "隐藏",
|
"chipActionHide": "隐藏",
|
||||||
"chipActionPin": "置顶",
|
"chipActionPin": "置顶",
|
||||||
"chipActionUnpin": "取消置顶",
|
"chipActionUnpin": "取消置顶",
|
||||||
|
@ -155,9 +157,9 @@
|
||||||
"displayRefreshRatePreferHighest": "最高刷新率",
|
"displayRefreshRatePreferHighest": "最高刷新率",
|
||||||
"displayRefreshRatePreferLowest": "最低刷新率",
|
"displayRefreshRatePreferLowest": "最低刷新率",
|
||||||
|
|
||||||
"slideshowVideoPlaybackSkip": "跳过",
|
"videoPlaybackSkip": "跳过",
|
||||||
"slideshowVideoPlaybackMuted": "静音播放",
|
"videoPlaybackMuted": "静音播放",
|
||||||
"slideshowVideoPlaybackWithSound": "带音播放",
|
"videoPlaybackWithSound": "带音播放",
|
||||||
|
|
||||||
"themeBrightnessLight": "浅色",
|
"themeBrightnessLight": "浅色",
|
||||||
"themeBrightnessDark": "深色",
|
"themeBrightnessDark": "深色",
|
||||||
|
@ -167,11 +169,15 @@
|
||||||
"viewerTransitionParallax": "视差滚动",
|
"viewerTransitionParallax": "视差滚动",
|
||||||
"viewerTransitionFade": "淡入淡出",
|
"viewerTransitionFade": "淡入淡出",
|
||||||
"viewerTransitionZoomIn": "放大",
|
"viewerTransitionZoomIn": "放大",
|
||||||
|
"viewerTransitionNone": "无",
|
||||||
|
|
||||||
"wallpaperTargetHome": "主屏幕",
|
"wallpaperTargetHome": "主屏幕",
|
||||||
"wallpaperTargetLock": "锁屏界面",
|
"wallpaperTargetLock": "锁屏界面",
|
||||||
"wallpaperTargetHomeLock": "主屏幕 + 锁屏界面",
|
"wallpaperTargetHomeLock": "主屏幕 + 锁屏界面",
|
||||||
|
|
||||||
|
"widgetOpenPageHome": "打开主页",
|
||||||
|
"widgetOpenPageViewer": "打开查看器",
|
||||||
|
|
||||||
"albumTierNew": "新的",
|
"albumTierNew": "新的",
|
||||||
"albumTierPinned": "钉选",
|
"albumTierPinned": "钉选",
|
||||||
"albumTierSpecial": "普通",
|
"albumTierSpecial": "普通",
|
||||||
|
@ -289,6 +295,7 @@
|
||||||
"viewDialogLayoutSectionTitle": "布局",
|
"viewDialogLayoutSectionTitle": "布局",
|
||||||
"viewDialogReverseSortOrder": "反向排序",
|
"viewDialogReverseSortOrder": "反向排序",
|
||||||
|
|
||||||
|
"tileLayoutMosaic": "马赛克",
|
||||||
"tileLayoutGrid": "网格",
|
"tileLayoutGrid": "网格",
|
||||||
"tileLayoutList": "列表",
|
"tileLayoutList": "列表",
|
||||||
|
|
||||||
|
@ -401,9 +408,12 @@
|
||||||
"sortOrderSmallestFirst": "由小到大",
|
"sortOrderSmallestFirst": "由小到大",
|
||||||
|
|
||||||
"albumGroupTier": "按层级",
|
"albumGroupTier": "按层级",
|
||||||
|
"albumGroupType": "按类型",
|
||||||
"albumGroupVolume": "按存储卷",
|
"albumGroupVolume": "按存储卷",
|
||||||
"albumGroupNone": "不分组",
|
"albumGroupNone": "不分组",
|
||||||
|
|
||||||
|
"albumMimeTypeMixed": "混合",
|
||||||
|
|
||||||
"albumPickPageTitleCopy": "复制到相册",
|
"albumPickPageTitleCopy": "复制到相册",
|
||||||
"albumPickPageTitleExport": "导出到相册",
|
"albumPickPageTitleExport": "导出到相册",
|
||||||
"albumPickPageTitleMove": "移至相册",
|
"albumPickPageTitleMove": "移至相册",
|
||||||
|
@ -442,6 +452,7 @@
|
||||||
"settingsPageTitle": "设置",
|
"settingsPageTitle": "设置",
|
||||||
"settingsSystemDefault": "系统",
|
"settingsSystemDefault": "系统",
|
||||||
"settingsDefault": "默认",
|
"settingsDefault": "默认",
|
||||||
|
"settingsDisabled": "禁用",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "搜索设置",
|
"settingsSearchFieldLabel": "搜索设置",
|
||||||
"settingsSearchEmpty": "无匹配设置项",
|
"settingsSearchEmpty": "无匹配设置项",
|
||||||
|
@ -524,10 +535,9 @@
|
||||||
"settingsSlideshowRepeat": "重复",
|
"settingsSlideshowRepeat": "重复",
|
||||||
"settingsSlideshowShuffle": "随机播放",
|
"settingsSlideshowShuffle": "随机播放",
|
||||||
"settingsSlideshowFillScreen": "填充屏幕",
|
"settingsSlideshowFillScreen": "填充屏幕",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect": "动画缩放效果",
|
||||||
"settingsSlideshowTransitionTile": "过渡动画",
|
"settingsSlideshowTransitionTile": "过渡动画",
|
||||||
"settingsSlideshowTransitionDialogTitle": "过渡动画",
|
|
||||||
"settingsSlideshowIntervalTile": "时间间隔",
|
"settingsSlideshowIntervalTile": "时间间隔",
|
||||||
"settingsSlideshowIntervalDialogTitle": "时间间隔",
|
|
||||||
"settingsSlideshowVideoPlaybackTile": "视频回放",
|
"settingsSlideshowVideoPlaybackTile": "视频回放",
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "视频回放",
|
"settingsSlideshowVideoPlaybackDialogTitle": "视频回放",
|
||||||
|
|
||||||
|
@ -535,7 +545,7 @@
|
||||||
"settingsVideoSectionTitle": "视频",
|
"settingsVideoSectionTitle": "视频",
|
||||||
"settingsVideoShowVideos": "显示视频",
|
"settingsVideoShowVideos": "显示视频",
|
||||||
"settingsVideoEnableHardwareAcceleration": "硬件加速",
|
"settingsVideoEnableHardwareAcceleration": "硬件加速",
|
||||||
"settingsVideoEnableAutoPlay": "自动播放",
|
"settingsVideoAutoPlay": "自动播放",
|
||||||
"settingsVideoLoopModeTile": "循环模式",
|
"settingsVideoLoopModeTile": "循环模式",
|
||||||
"settingsVideoLoopModeDialogTitle": "循环模式",
|
"settingsVideoLoopModeDialogTitle": "循环模式",
|
||||||
|
|
||||||
|
@ -557,7 +567,6 @@
|
||||||
"settingsVideoControlsTile": "控件",
|
"settingsVideoControlsTile": "控件",
|
||||||
"settingsVideoControlsPageTitle": "控件",
|
"settingsVideoControlsPageTitle": "控件",
|
||||||
"settingsVideoButtonsTile": "按钮",
|
"settingsVideoButtonsTile": "按钮",
|
||||||
"settingsVideoButtonsDialogTitle": "按钮",
|
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "双击播放/暂停",
|
"settingsVideoGestureDoubleTapTogglePlay": "双击播放/暂停",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "双击屏幕边缘步进/步退",
|
"settingsVideoGestureSideDoubleTapSeek": "双击屏幕边缘步进/步退",
|
||||||
|
|
||||||
|
@ -590,7 +599,6 @@
|
||||||
"settingsRemoveAnimationsTile": "移除动画",
|
"settingsRemoveAnimationsTile": "移除动画",
|
||||||
"settingsRemoveAnimationsDialogTitle": "移除动画",
|
"settingsRemoveAnimationsDialogTitle": "移除动画",
|
||||||
"settingsTimeToTakeActionTile": "生效时间",
|
"settingsTimeToTakeActionTile": "生效时间",
|
||||||
"settingsTimeToTakeActionDialogTitle": "生效时间",
|
|
||||||
|
|
||||||
"settingsDisplaySectionTitle": "显示",
|
"settingsDisplaySectionTitle": "显示",
|
||||||
"settingsThemeBrightnessTile": "主题",
|
"settingsThemeBrightnessTile": "主题",
|
||||||
|
@ -612,6 +620,7 @@
|
||||||
|
|
||||||
"settingsWidgetPageTitle": "相框",
|
"settingsWidgetPageTitle": "相框",
|
||||||
"settingsWidgetShowOutline": "轮廓",
|
"settingsWidgetShowOutline": "轮廓",
|
||||||
|
"settingsWidgetOpenPage": "轻触小部件时",
|
||||||
|
|
||||||
"settingsCollectionTile": "媒体集",
|
"settingsCollectionTile": "媒体集",
|
||||||
|
|
||||||
|
@ -620,6 +629,7 @@
|
||||||
"statsTopCountriesSectionTitle": "热门国家",
|
"statsTopCountriesSectionTitle": "热门国家",
|
||||||
"statsTopPlacesSectionTitle": "热门地点",
|
"statsTopPlacesSectionTitle": "热门地点",
|
||||||
"statsTopTagsSectionTitle": "热门标签",
|
"statsTopTagsSectionTitle": "热门标签",
|
||||||
|
"statsTopAlbumsSectionTitle": "热门相册",
|
||||||
|
|
||||||
"viewerOpenPanoramaButtonLabel": "打开全景",
|
"viewerOpenPanoramaButtonLabel": "打开全景",
|
||||||
"viewerSetWallpaperButtonLabel": "设置壁纸",
|
"viewerSetWallpaperButtonLabel": "设置壁纸",
|
||||||
|
@ -664,6 +674,8 @@
|
||||||
"viewerInfoSearchSuggestionResolution": "分辨率",
|
"viewerInfoSearchSuggestionResolution": "分辨率",
|
||||||
"viewerInfoSearchSuggestionRights": "所有权",
|
"viewerInfoSearchSuggestionRights": "所有权",
|
||||||
|
|
||||||
|
"wallpaperUseScrollEffect": "在主屏幕上使用滚动效果",
|
||||||
|
|
||||||
"tagEditorPageTitle": "编辑标签",
|
"tagEditorPageTitle": "编辑标签",
|
||||||
"tagEditorPageNewTagFieldLabel": "新标签",
|
"tagEditorPageNewTagFieldLabel": "新标签",
|
||||||
"tagEditorPageAddTagTooltip": "添加标签",
|
"tagEditorPageAddTagTooltip": "添加标签",
|
||||||
|
|
|
@ -33,6 +33,7 @@ void mainCommon(AppFlavor flavor) {
|
||||||
// - in profile/release mode: plain grey background
|
// - in profile/release mode: plain grey background
|
||||||
// This can be modified via `ErrorWidget.builder`
|
// This can be modified via `ErrorWidget.builder`
|
||||||
// ErrorWidget.builder = (details) => ErrorWidget(details.exception);
|
// ErrorWidget.builder = (details) => ErrorWidget(details.exception);
|
||||||
|
// cf https://docs.flutter.dev/testing/errors
|
||||||
|
|
||||||
runApp(AvesApp(flavor: flavor));
|
runApp(AvesApp(flavor: flavor));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ enum ChipAction {
|
||||||
goToAlbumPage,
|
goToAlbumPage,
|
||||||
goToCountryPage,
|
goToCountryPage,
|
||||||
goToTagPage,
|
goToTagPage,
|
||||||
|
reverse,
|
||||||
hide,
|
hide,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +19,9 @@ extension ExtraChipAction on ChipAction {
|
||||||
return context.l10n.chipActionGoToCountryPage;
|
return context.l10n.chipActionGoToCountryPage;
|
||||||
case ChipAction.goToTagPage:
|
case ChipAction.goToTagPage:
|
||||||
return context.l10n.chipActionGoToTagPage;
|
return context.l10n.chipActionGoToTagPage;
|
||||||
|
case ChipAction.reverse:
|
||||||
|
// different data depending on state
|
||||||
|
return context.l10n.chipActionFilterOut;
|
||||||
case ChipAction.hide:
|
case ChipAction.hide:
|
||||||
return context.l10n.chipActionHide;
|
return context.l10n.chipActionHide;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +37,8 @@ extension ExtraChipAction on ChipAction {
|
||||||
return AIcons.location;
|
return AIcons.location;
|
||||||
case ChipAction.goToTagPage:
|
case ChipAction.goToTagPage:
|
||||||
return AIcons.tag;
|
return AIcons.tag;
|
||||||
|
case ChipAction.reverse:
|
||||||
|
return AIcons.reverse;
|
||||||
case ChipAction.hide:
|
case ChipAction.hide:
|
||||||
return AIcons.hide;
|
return AIcons.hide;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ class EntrySetActions {
|
||||||
];
|
];
|
||||||
|
|
||||||
// exclude bin related actions
|
// exclude bin related actions
|
||||||
static const collectionEditorSelection = [
|
static const collectionEditorSelectionRegular = [
|
||||||
EntrySetAction.share,
|
EntrySetAction.share,
|
||||||
EntrySetAction.delete,
|
EntrySetAction.delete,
|
||||||
EntrySetAction.copy,
|
EntrySetAction.copy,
|
||||||
|
@ -97,6 +97,18 @@ class EntrySetActions {
|
||||||
// editing actions are in their subsection
|
// editing actions are in their subsection
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static const collectionEditorSelectionEdit = [
|
||||||
|
EntrySetAction.rotateCCW,
|
||||||
|
EntrySetAction.rotateCW,
|
||||||
|
EntrySetAction.flip,
|
||||||
|
EntrySetAction.editDate,
|
||||||
|
EntrySetAction.editLocation,
|
||||||
|
EntrySetAction.editTitleDescription,
|
||||||
|
EntrySetAction.editRating,
|
||||||
|
EntrySetAction.editTags,
|
||||||
|
EntrySetAction.removeMetadata,
|
||||||
|
];
|
||||||
|
|
||||||
static const edit = [
|
static const edit = [
|
||||||
EntrySetAction.editDate,
|
EntrySetAction.editDate,
|
||||||
EntrySetAction.editLocation,
|
EntrySetAction.editLocation,
|
||||||
|
|
|
@ -10,8 +10,6 @@ import 'package:aves/model/video_playback.dart';
|
||||||
abstract class MetadataDb {
|
abstract class MetadataDb {
|
||||||
int get nextId;
|
int get nextId;
|
||||||
|
|
||||||
int get timestampSecs;
|
|
||||||
|
|
||||||
Future<void> init();
|
Future<void> init();
|
||||||
|
|
||||||
Future<int> dbFileSize();
|
Future<int> dbFileSize();
|
||||||
|
|
|
@ -34,9 +34,6 @@ class SqfliteMetadataDb implements MetadataDb {
|
||||||
@override
|
@override
|
||||||
int get nextId => ++_lastId;
|
int get nextId => ++_lastId;
|
||||||
|
|
||||||
@override
|
|
||||||
int get timestampSecs => DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_db = await openDatabase(
|
_db = await openDatabase(
|
||||||
|
|
|
@ -26,7 +26,7 @@ import 'package:country_code/country_code.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
enum EntryDataType { basic, catalog, address, references }
|
enum EntryDataType { basic, aspectRatio, catalog, address, references }
|
||||||
|
|
||||||
class AvesEntry {
|
class AvesEntry {
|
||||||
// `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode
|
// `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode
|
||||||
|
@ -150,6 +150,7 @@ class AvesEntry {
|
||||||
'sourceRotationDegrees': sourceRotationDegrees,
|
'sourceRotationDegrees': sourceRotationDegrees,
|
||||||
'sizeBytes': sizeBytes,
|
'sizeBytes': sizeBytes,
|
||||||
'title': sourceTitle,
|
'title': sourceTitle,
|
||||||
|
'dateAddedSecs': dateAddedSecs,
|
||||||
'dateModifiedSecs': dateModifiedSecs,
|
'dateModifiedSecs': dateModifiedSecs,
|
||||||
'sourceDateTakenMillis': sourceDateTakenMillis,
|
'sourceDateTakenMillis': sourceDateTakenMillis,
|
||||||
'durationMillis': durationMillis,
|
'durationMillis': durationMillis,
|
||||||
|
@ -277,6 +278,8 @@ class AvesEntry {
|
||||||
|
|
||||||
bool get isMediaStoreContent => uri.startsWith('content://media/');
|
bool get isMediaStoreContent => uri.startsWith('content://media/');
|
||||||
|
|
||||||
|
bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains);
|
||||||
|
|
||||||
bool get canEdit => path != null && !trashed && isMediaStoreContent;
|
bool get canEdit => path != null && !trashed && isMediaStoreContent;
|
||||||
|
|
||||||
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
|
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
|
||||||
|
@ -291,9 +294,9 @@ class AvesEntry {
|
||||||
|
|
||||||
bool get canRotateAndFlip => canEdit && canEditExif;
|
bool get canRotateAndFlip => canEdit && canEditExif;
|
||||||
|
|
||||||
// as of androidx.exifinterface:exifinterface:1.3.3
|
// `exifinterface` v1.3.3 declared support for DNG, but it strips non-standard Exif tags when saving attributes,
|
||||||
// `exifinterface` declares support for DNG, but `exifinterface` strips non-standard Exif tags when saving attributes,
|
// and DNG requires DNG-specific tags saved along standard Exif. So it was actually breaking DNG files.
|
||||||
// and DNG requires DNG-specific tags saved along standard Exif. So `exifinterface` actually breaks DNG files.
|
// as of androidx.exifinterface:exifinterface:1.3.4
|
||||||
bool get canEditExif {
|
bool get canEditExif {
|
||||||
switch (mimeType.toLowerCase()) {
|
switch (mimeType.toLowerCase()) {
|
||||||
case MimeTypes.jpeg:
|
case MimeTypes.jpeg:
|
||||||
|
|
|
@ -126,6 +126,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
if (newFields.isNotEmpty) {
|
if (newFields.isNotEmpty) {
|
||||||
dataTypes.addAll({
|
dataTypes.addAll({
|
||||||
EntryDataType.basic,
|
EntryDataType.basic,
|
||||||
|
EntryDataType.aspectRatio,
|
||||||
EntryDataType.catalog,
|
EntryDataType.catalog,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -309,14 +310,18 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<EntryDataType>> removeMetadata(Set<MetadataType> types) async {
|
Future<Set<EntryDataType>> removeMetadata(Set<MetadataType> types) async {
|
||||||
|
final Set<EntryDataType> dataTypes = {};
|
||||||
|
|
||||||
final newFields = await metadataEditService.removeTypes(this, types);
|
final newFields = await metadataEditService.removeTypes(this, types);
|
||||||
return newFields.isEmpty
|
if (newFields.isNotEmpty) {
|
||||||
? {}
|
dataTypes.addAll({
|
||||||
: {
|
EntryDataType.basic,
|
||||||
EntryDataType.basic,
|
EntryDataType.aspectRatio,
|
||||||
EntryDataType.catalog,
|
EntryDataType.catalog,
|
||||||
EntryDataType.address,
|
EntryDataType.address,
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
return dataTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void editIptcValues(List<Map<String, dynamic>> iptc, int record, int tag, Set<String> values) {
|
static void editIptcValues(List<Map<String, dynamic>> iptc, int record, int tag, Set<String> values) {
|
||||||
|
|
|
@ -14,16 +14,20 @@ class AlbumFilter extends CoveredCollectionFilter {
|
||||||
|
|
||||||
final String album;
|
final String album;
|
||||||
final String? displayName;
|
final String? displayName;
|
||||||
|
late final EntryFilter _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [album];
|
List<Object?> get props => [album, reversed];
|
||||||
|
|
||||||
const AlbumFilter(this.album, this.displayName);
|
AlbumFilter(this.album, this.displayName, {super.reversed = false}) {
|
||||||
|
_test = (entry) => entry.directory == album;
|
||||||
|
}
|
||||||
|
|
||||||
factory AlbumFilter.fromMap(Map<String, dynamic> json) {
|
factory AlbumFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return AlbumFilter(
|
return AlbumFilter(
|
||||||
json['album'],
|
json['album'],
|
||||||
json['uniqueName'],
|
json['uniqueName'],
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +36,14 @@ class AlbumFilter extends CoveredCollectionFilter {
|
||||||
'type': type,
|
'type': type,
|
||||||
'album': album,
|
'album': album,
|
||||||
'uniqueName': displayName,
|
'uniqueName': displayName,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => (entry) => entry.directory == album;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => displayName ?? pContext.split(album).last;
|
String get universalLabel => displayName ?? pContext.split(album).last;
|
||||||
|
|
|
@ -17,16 +17,20 @@ class CoordinateFilter extends CollectionFilter {
|
||||||
final LatLng sw;
|
final LatLng sw;
|
||||||
final LatLng ne;
|
final LatLng ne;
|
||||||
final bool minuteSecondPadding;
|
final bool minuteSecondPadding;
|
||||||
|
late final EntryFilter _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [sw, ne];
|
List<Object?> get props => [sw, ne, reversed];
|
||||||
|
|
||||||
const CoordinateFilter(this.sw, this.ne, {this.minuteSecondPadding = false});
|
CoordinateFilter(this.sw, this.ne, {this.minuteSecondPadding = false, super.reversed = false}) {
|
||||||
|
_test = (entry) => GeoUtils.contains(sw, ne, entry.latLng);
|
||||||
|
}
|
||||||
|
|
||||||
factory CoordinateFilter.fromMap(Map<String, dynamic> json) {
|
factory CoordinateFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return CoordinateFilter(
|
return CoordinateFilter(
|
||||||
LatLng.fromJson(json['sw']),
|
LatLng.fromJson(json['sw']),
|
||||||
LatLng.fromJson(json['ne']),
|
LatLng.fromJson(json['ne']),
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,10 +39,11 @@ class CoordinateFilter extends CollectionFilter {
|
||||||
'type': type,
|
'type': type,
|
||||||
'sw': sw.toJson(),
|
'sw': sw.toJson(),
|
||||||
'ne': ne.toJson(),
|
'ne': ne.toJson(),
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => (entry) => GeoUtils.contains(sw, ne, entry.latLng);
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
String _formatBounds(AppLocalizations l10n, CoordinateFormat format) {
|
String _formatBounds(AppLocalizations l10n, CoordinateFormat format) {
|
||||||
String s(LatLng latLng) => format.format(
|
String s(LatLng latLng) => format.format(
|
||||||
|
@ -50,6 +55,9 @@ class CoordinateFilter extends CollectionFilter {
|
||||||
return '${s(ne)}\n${s(sw)}';
|
return '${s(ne)}\n${s(sw)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => _formatBounds(lookupAppLocalizations(AppLocalizations.supportedLocales.first), CoordinateFormat.decimal);
|
String get universalLabel => _formatBounds(lookupAppLocalizations(AppLocalizations.supportedLocales.first), CoordinateFormat.decimal);
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@ class DateFilter extends CollectionFilter {
|
||||||
static final onThisDay = DateFilter(DateLevel.md, null);
|
static final onThisDay = DateFilter(DateLevel.md, null);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [level, date];
|
List<Object?> get props => [level, date, reversed];
|
||||||
|
|
||||||
DateFilter(this.level, this.date) {
|
DateFilter(this.level, this.date, {super.reversed = false}) {
|
||||||
_effectiveDate = date ?? DateTime.now();
|
_effectiveDate = date ?? DateTime.now();
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case DateLevel.y:
|
case DateLevel.y:
|
||||||
|
@ -56,6 +56,7 @@ class DateFilter extends CollectionFilter {
|
||||||
return DateFilter(
|
return DateFilter(
|
||||||
DateLevel.values.firstWhereOrNull((v) => v.toString() == json['level']) ?? DateLevel.ymd,
|
DateLevel.values.firstWhereOrNull((v) => v.toString() == json['level']) ?? DateLevel.ymd,
|
||||||
dateString != null ? DateTime.tryParse(dateString) : null,
|
dateString != null ? DateTime.tryParse(dateString) : null,
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,15 +65,20 @@ class DateFilter extends CollectionFilter {
|
||||||
'type': type,
|
'type': type,
|
||||||
'level': level.toString(),
|
'level': level.toString(),
|
||||||
'date': date?.toIso8601String(),
|
'date': date?.toIso8601String(),
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => _test;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isCompatible(CollectionFilter other) {
|
bool isCompatible(CollectionFilter other) {
|
||||||
if (other is DateFilter) {
|
if (other is DateFilter) {
|
||||||
return isCompatibleLevel(level, other.level);
|
if (reversed != other.reversed && this == other.reverse()) return false;
|
||||||
|
return reversed || other.reversed || isCompatibleLevel(level, other.level);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
@ -9,20 +10,32 @@ import 'package:provider/provider.dart';
|
||||||
class FavouriteFilter extends CollectionFilter {
|
class FavouriteFilter extends CollectionFilter {
|
||||||
static const type = 'favourite';
|
static const type = 'favourite';
|
||||||
|
|
||||||
|
static bool _test(AvesEntry entry) => entry.isFavourite;
|
||||||
|
|
||||||
static const instance = FavouriteFilter._private();
|
static const instance = FavouriteFilter._private();
|
||||||
|
static const instanceReversed = FavouriteFilter._private(reversed: true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [];
|
List<Object?> get props => [reversed];
|
||||||
|
|
||||||
const FavouriteFilter._private();
|
const FavouriteFilter._private({super.reversed = false});
|
||||||
|
|
||||||
|
factory FavouriteFilter.fromMap(Map<String, dynamic> json) {
|
||||||
|
final reversed = json['reversed'] ?? false;
|
||||||
|
return reversed ? instanceReversed : instance;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => (entry) => entry.isFavourite;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => type;
|
String get universalLabel => type;
|
||||||
|
|
|
@ -42,9 +42,44 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
||||||
PathFilter.type,
|
PathFilter.type,
|
||||||
];
|
];
|
||||||
|
|
||||||
final bool not;
|
final bool reversed;
|
||||||
|
|
||||||
const CollectionFilter({this.not = false});
|
const CollectionFilter({required this.reversed});
|
||||||
|
|
||||||
|
static CollectionFilter? _fromMap(Map<String, dynamic> jsonMap) {
|
||||||
|
final type = jsonMap['type'];
|
||||||
|
switch (type) {
|
||||||
|
case AlbumFilter.type:
|
||||||
|
return AlbumFilter.fromMap(jsonMap);
|
||||||
|
case CoordinateFilter.type:
|
||||||
|
return CoordinateFilter.fromMap(jsonMap);
|
||||||
|
case DateFilter.type:
|
||||||
|
return DateFilter.fromMap(jsonMap);
|
||||||
|
case FavouriteFilter.type:
|
||||||
|
return FavouriteFilter.fromMap(jsonMap);
|
||||||
|
case LocationFilter.type:
|
||||||
|
return LocationFilter.fromMap(jsonMap);
|
||||||
|
case MimeFilter.type:
|
||||||
|
return MimeFilter.fromMap(jsonMap);
|
||||||
|
case MissingFilter.type:
|
||||||
|
return MissingFilter.fromMap(jsonMap);
|
||||||
|
case PathFilter.type:
|
||||||
|
return PathFilter.fromMap(jsonMap);
|
||||||
|
case QueryFilter.type:
|
||||||
|
return QueryFilter.fromMap(jsonMap);
|
||||||
|
case RatingFilter.type:
|
||||||
|
return RatingFilter.fromMap(jsonMap);
|
||||||
|
case RecentlyAddedFilter.type:
|
||||||
|
return RecentlyAddedFilter.fromMap(jsonMap);
|
||||||
|
case TagFilter.type:
|
||||||
|
return TagFilter.fromMap(jsonMap);
|
||||||
|
case TypeFilter.type:
|
||||||
|
return TypeFilter.fromMap(jsonMap);
|
||||||
|
case TrashFilter.type:
|
||||||
|
return TrashFilter.fromMap(jsonMap);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static CollectionFilter? fromJson(String jsonString) {
|
static CollectionFilter? fromJson(String jsonString) {
|
||||||
if (jsonString.isEmpty) return null;
|
if (jsonString.isEmpty) return null;
|
||||||
|
@ -52,37 +87,7 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
||||||
try {
|
try {
|
||||||
final jsonMap = jsonDecode(jsonString);
|
final jsonMap = jsonDecode(jsonString);
|
||||||
if (jsonMap is Map<String, dynamic>) {
|
if (jsonMap is Map<String, dynamic>) {
|
||||||
final type = jsonMap['type'];
|
return _fromMap(jsonMap);
|
||||||
switch (type) {
|
|
||||||
case AlbumFilter.type:
|
|
||||||
return AlbumFilter.fromMap(jsonMap);
|
|
||||||
case CoordinateFilter.type:
|
|
||||||
return CoordinateFilter.fromMap(jsonMap);
|
|
||||||
case DateFilter.type:
|
|
||||||
return DateFilter.fromMap(jsonMap);
|
|
||||||
case FavouriteFilter.type:
|
|
||||||
return FavouriteFilter.instance;
|
|
||||||
case LocationFilter.type:
|
|
||||||
return LocationFilter.fromMap(jsonMap);
|
|
||||||
case MimeFilter.type:
|
|
||||||
return MimeFilter.fromMap(jsonMap);
|
|
||||||
case MissingFilter.type:
|
|
||||||
return MissingFilter.fromMap(jsonMap);
|
|
||||||
case PathFilter.type:
|
|
||||||
return PathFilter.fromMap(jsonMap);
|
|
||||||
case QueryFilter.type:
|
|
||||||
return QueryFilter.fromMap(jsonMap);
|
|
||||||
case RatingFilter.type:
|
|
||||||
return RatingFilter.fromMap(jsonMap);
|
|
||||||
case RecentlyAddedFilter.type:
|
|
||||||
return RecentlyAddedFilter.instance;
|
|
||||||
case TagFilter.type:
|
|
||||||
return TagFilter.fromMap(jsonMap);
|
|
||||||
case TypeFilter.type:
|
|
||||||
return TypeFilter.fromMap(jsonMap);
|
|
||||||
case TrashFilter.type:
|
|
||||||
return TrashFilter.instance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
debugPrint('failed to parse filter from json=$jsonString error=$error\n$stack');
|
debugPrint('failed to parse filter from json=$jsonString error=$error\n$stack');
|
||||||
|
@ -95,9 +100,21 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
||||||
|
|
||||||
String toJson() => jsonEncode(toMap());
|
String toJson() => jsonEncode(toMap());
|
||||||
|
|
||||||
EntryFilter get test;
|
EntryFilter get positiveTest;
|
||||||
|
|
||||||
bool isCompatible(CollectionFilter other) => category != other.category;
|
EntryFilter get test => reversed ? (v) => !positiveTest(v) : positiveTest;
|
||||||
|
|
||||||
|
CollectionFilter reverse() => _fromMap(toMap()..['reversed'] = !reversed)!;
|
||||||
|
|
||||||
|
bool get exclusiveProp;
|
||||||
|
|
||||||
|
bool isCompatible(CollectionFilter other) {
|
||||||
|
if (category != other.category) return true;
|
||||||
|
if (!reversed && !other.reversed) return !exclusiveProp;
|
||||||
|
if (reversed && other.reversed) return true;
|
||||||
|
if (this == other.reverse()) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
String get universalLabel;
|
String get universalLabel;
|
||||||
|
|
||||||
|
@ -129,7 +146,7 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
abstract class CoveredCollectionFilter extends CollectionFilter {
|
abstract class CoveredCollectionFilter extends CollectionFilter {
|
||||||
const CoveredCollectionFilter({bool not = false}) : super(not: not);
|
const CoveredCollectionFilter({required super.reversed});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Color> color(BuildContext context) {
|
Future<Color> color(BuildContext context) {
|
||||||
|
|
|
@ -15,9 +15,9 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
late final EntryFilter _test;
|
late final EntryFilter _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [level, _location, _countryCode];
|
List<Object?> get props => [level, _location, _countryCode, reversed];
|
||||||
|
|
||||||
LocationFilter(this.level, String location) {
|
LocationFilter(this.level, String location, {super.reversed = false}) {
|
||||||
final split = location.split(locationSeparator);
|
final split = location.split(locationSeparator);
|
||||||
_location = split.isNotEmpty ? split[0] : location;
|
_location = split.isNotEmpty ? split[0] : location;
|
||||||
_countryCode = split.length > 1 ? split[1] : null;
|
_countryCode = split.length > 1 ? split[1] : null;
|
||||||
|
@ -35,6 +35,7 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
return LocationFilter(
|
return LocationFilter(
|
||||||
LocationLevel.values.firstWhereOrNull((v) => v.toString() == json['level']) ?? LocationLevel.place,
|
LocationLevel.values.firstWhereOrNull((v) => v.toString() == json['level']) ?? LocationLevel.place,
|
||||||
json['location'],
|
json['location'],
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
'type': type,
|
'type': type,
|
||||||
'level': level.toString(),
|
'level': level.toString(),
|
||||||
'location': _countryCode != null ? countryNameAndCode : _location,
|
'location': _countryCode != null ? countryNameAndCode : _location,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
String get countryNameAndCode => '$_location$locationSeparator$_countryCode';
|
String get countryNameAndCode => '$_location$locationSeparator$_countryCode';
|
||||||
|
@ -50,7 +52,10 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
String? get countryCode => _countryCode;
|
String? get countryCode => _countryCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => _test;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => _location;
|
String get universalLabel => _location;
|
||||||
|
|
|
@ -14,17 +14,17 @@ class MimeFilter extends CollectionFilter {
|
||||||
static const type = 'mime';
|
static const type = 'mime';
|
||||||
|
|
||||||
final String mime;
|
final String mime;
|
||||||
late final EntryFilter _test;
|
|
||||||
late final String _label;
|
late final String _label;
|
||||||
late final IconData _icon;
|
late final IconData _icon;
|
||||||
|
late final EntryFilter _test;
|
||||||
|
|
||||||
static final image = MimeFilter(MimeTypes.anyImage);
|
static final image = MimeFilter(MimeTypes.anyImage);
|
||||||
static final video = MimeFilter(MimeTypes.anyVideo);
|
static final video = MimeFilter(MimeTypes.anyVideo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [mime];
|
List<Object?> get props => [mime, reversed];
|
||||||
|
|
||||||
MimeFilter(this.mime) {
|
MimeFilter(this.mime, {super.reversed = false}) {
|
||||||
IconData? icon;
|
IconData? icon;
|
||||||
var lowMime = mime.toLowerCase();
|
var lowMime = mime.toLowerCase();
|
||||||
if (lowMime.endsWith('/*')) {
|
if (lowMime.endsWith('/*')) {
|
||||||
|
@ -46,6 +46,7 @@ class MimeFilter extends CollectionFilter {
|
||||||
factory MimeFilter.fromMap(Map<String, dynamic> json) {
|
factory MimeFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return MimeFilter(
|
return MimeFilter(
|
||||||
json['mime'],
|
json['mime'],
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,10 +54,14 @@ class MimeFilter extends CollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'mime': mime,
|
'mime': mime,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => _test;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => _label;
|
String get universalLabel => _label;
|
||||||
|
|
|
@ -10,16 +10,16 @@ class MissingFilter extends CollectionFilter {
|
||||||
static const _title = 'title';
|
static const _title = 'title';
|
||||||
|
|
||||||
final String metadataType;
|
final String metadataType;
|
||||||
late final EntryFilter _test;
|
|
||||||
late final IconData _icon;
|
late final IconData _icon;
|
||||||
|
late final EntryFilter _test;
|
||||||
|
|
||||||
static final date = MissingFilter._private(_date);
|
static final date = MissingFilter._private(_date);
|
||||||
static final title = MissingFilter._private(_title);
|
static final title = MissingFilter._private(_title);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [metadataType];
|
List<Object?> get props => [metadataType, reversed];
|
||||||
|
|
||||||
MissingFilter._private(this.metadataType) {
|
MissingFilter._private(this.metadataType, {super.reversed = false}) {
|
||||||
switch (metadataType) {
|
switch (metadataType) {
|
||||||
case _date:
|
case _date:
|
||||||
_test = (entry) => (entry.catalogMetadata?.dateMillis ?? 0) == 0;
|
_test = (entry) => (entry.catalogMetadata?.dateMillis ?? 0) == 0;
|
||||||
|
@ -35,6 +35,7 @@ class MissingFilter extends CollectionFilter {
|
||||||
factory MissingFilter.fromMap(Map<String, dynamic> json) {
|
factory MissingFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return MissingFilter._private(
|
return MissingFilter._private(
|
||||||
json['metadataType'],
|
json['metadataType'],
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +43,14 @@ class MissingFilter extends CollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'metadataType': metadataType,
|
'metadataType': metadataType,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => _test;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => metadataType;
|
String get universalLabel => metadataType;
|
||||||
|
|
|
@ -10,14 +10,24 @@ class PathFilter extends CollectionFilter {
|
||||||
// without trailing separator
|
// without trailing separator
|
||||||
final String _rootAlbum;
|
final String _rootAlbum;
|
||||||
|
|
||||||
@override
|
late final EntryFilter _test;
|
||||||
List<Object?> get props => [path];
|
|
||||||
|
|
||||||
PathFilter(this.path) : _rootAlbum = path.substring(0, path.length - 1);
|
@override
|
||||||
|
List<Object?> get props => [path, reversed];
|
||||||
|
|
||||||
|
PathFilter(this.path, {super.reversed = false}) : _rootAlbum = path.substring(0, path.length - 1) {
|
||||||
|
_test = (entry) {
|
||||||
|
final dir = entry.directory;
|
||||||
|
if (dir == null) return false;
|
||||||
|
// avoid string building in most cases
|
||||||
|
return dir.startsWith(_rootAlbum) && '$dir${pContext.separator}'.startsWith(path);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
factory PathFilter.fromMap(Map<String, dynamic> json) {
|
factory PathFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return PathFilter(
|
return PathFilter(
|
||||||
json['path'],
|
json['path'],
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,15 +35,14 @@ class PathFilter extends CollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'path': path,
|
'path': path,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => (entry) {
|
EntryFilter get positiveTest => _test;
|
||||||
final dir = entry.directory;
|
|
||||||
if (dir == null) return false;
|
@override
|
||||||
// avoid string building in most cases
|
bool get exclusiveProp => true;
|
||||||
return dir.startsWith(_rootAlbum) && '$dir${pContext.separator}'.startsWith(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => path;
|
String get universalLabel => path;
|
||||||
|
|
|
@ -18,7 +18,7 @@ class QueryFilter extends CollectionFilter {
|
||||||
late final EntryFilter _test;
|
late final EntryFilter _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [query, live];
|
List<Object?> get props => [query, live, reversed];
|
||||||
|
|
||||||
static final _fieldPattern = RegExp(r'(.+)([=<>])(.+)');
|
static final _fieldPattern = RegExp(r'(.+)([=<>])(.+)');
|
||||||
static final _fileSizePattern = RegExp(r'(\d+)([KMG])?');
|
static final _fileSizePattern = RegExp(r'(\d+)([KMG])?');
|
||||||
|
@ -33,7 +33,7 @@ class QueryFilter extends CollectionFilter {
|
||||||
static const opLower = '<';
|
static const opLower = '<';
|
||||||
static const opGreater = '>';
|
static const opGreater = '>';
|
||||||
|
|
||||||
QueryFilter(this.query, {this.colorful = true, this.live = false}) {
|
QueryFilter(this.query, {this.colorful = true, this.live = false, super.reversed = false}) {
|
||||||
var upQuery = query.toUpperCase();
|
var upQuery = query.toUpperCase();
|
||||||
|
|
||||||
final test = fieldTest(upQuery);
|
final test = fieldTest(upQuery);
|
||||||
|
@ -62,6 +62,7 @@ class QueryFilter extends CollectionFilter {
|
||||||
factory QueryFilter.fromMap(Map<String, dynamic> json) {
|
factory QueryFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return QueryFilter(
|
return QueryFilter(
|
||||||
json['query'],
|
json['query'],
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,13 +70,14 @@ class QueryFilter extends CollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'query': query,
|
'query': query,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => _test;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isCompatible(CollectionFilter other) => true;
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => query;
|
String get universalLabel => query;
|
||||||
|
|
|
@ -7,15 +7,19 @@ class RatingFilter extends CollectionFilter {
|
||||||
static const type = 'rating';
|
static const type = 'rating';
|
||||||
|
|
||||||
final int rating;
|
final int rating;
|
||||||
|
late final EntryFilter _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [rating];
|
List<Object?> get props => [rating, reversed];
|
||||||
|
|
||||||
const RatingFilter(this.rating);
|
RatingFilter(this.rating, {super.reversed = false}) {
|
||||||
|
_test = (entry) => entry.rating == rating;
|
||||||
|
}
|
||||||
|
|
||||||
factory RatingFilter.fromMap(Map<String, dynamic> json) {
|
factory RatingFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return RatingFilter(
|
return RatingFilter(
|
||||||
json['rating'] ?? 0,
|
json['rating'] ?? 0,
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,10 +27,14 @@ class RatingFilter extends CollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'rating': rating,
|
'rating': rating,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => (entry) => entry.rating == rating;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => '$rating';
|
String get universalLabel => '$rating';
|
||||||
|
|
|
@ -6,30 +6,43 @@ import 'package:flutter/material.dart';
|
||||||
class RecentlyAddedFilter extends CollectionFilter {
|
class RecentlyAddedFilter extends CollectionFilter {
|
||||||
static const type = 'recently_added';
|
static const type = 'recently_added';
|
||||||
|
|
||||||
|
static late EntryFilter _test;
|
||||||
|
|
||||||
static final instance = RecentlyAddedFilter._private();
|
static final instance = RecentlyAddedFilter._private();
|
||||||
|
static final instanceReversed = RecentlyAddedFilter._private(reversed: true);
|
||||||
|
|
||||||
static late int nowSecs;
|
static late int nowSecs;
|
||||||
|
|
||||||
static void updateNow() {
|
static void updateNow() {
|
||||||
nowSecs = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
nowSecs = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
_test = (entry) => (nowSecs - (entry.dateAddedSecs ?? 0)) < _dayInSecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const _dayInSecs = 24 * 60 * 60;
|
static const _dayInSecs = 24 * 60 * 60;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [];
|
List<Object?> get props => [reversed];
|
||||||
|
|
||||||
RecentlyAddedFilter._private() {
|
RecentlyAddedFilter._private({super.reversed = false}) {
|
||||||
updateNow();
|
updateNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory RecentlyAddedFilter.fromMap(Map<String, dynamic> json) {
|
||||||
|
final reversed = json['reversed'] ?? false;
|
||||||
|
return reversed ? instanceReversed : instance;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => (entry) => (nowSecs - (entry.dateAddedSecs ?? 0)) < _dayInSecs;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => type;
|
String get universalLabel => type;
|
||||||
|
|
|
@ -10,20 +10,20 @@ class TagFilter extends CoveredCollectionFilter {
|
||||||
late final EntryFilter _test;
|
late final EntryFilter _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [tag];
|
List<Object?> get props => [tag, reversed];
|
||||||
|
|
||||||
TagFilter(this.tag, {bool not = false}) : super(not: not) {
|
TagFilter(this.tag, {super.reversed = false}) {
|
||||||
if (tag.isEmpty) {
|
if (tag.isEmpty) {
|
||||||
_test = not ? (entry) => entry.tags.isNotEmpty : (entry) => entry.tags.isEmpty;
|
_test = (entry) => entry.tags.isEmpty;
|
||||||
} else {
|
} else {
|
||||||
_test = not ? (entry) => !entry.tags.contains(tag) : (entry) => entry.tags.contains(tag);
|
_test = (entry) => entry.tags.contains(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
factory TagFilter.fromMap(Map<String, dynamic> json) {
|
factory TagFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return TagFilter(
|
return TagFilter(
|
||||||
json['tag'],
|
json['tag'],
|
||||||
not: json['not'] ?? false,
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,14 +31,14 @@ class TagFilter extends CoveredCollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'tag': tag,
|
'tag': tag,
|
||||||
'not': not,
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => _test;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isCompatible(CollectionFilter other) => true;
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => tag;
|
String get universalLabel => tag;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -6,20 +7,32 @@ import 'package:flutter/material.dart';
|
||||||
class TrashFilter extends CollectionFilter {
|
class TrashFilter extends CollectionFilter {
|
||||||
static const type = 'trash';
|
static const type = 'trash';
|
||||||
|
|
||||||
|
static bool _test(AvesEntry entry) => entry.trashed;
|
||||||
|
|
||||||
static const instance = TrashFilter._private();
|
static const instance = TrashFilter._private();
|
||||||
|
static const instanceReversed = TrashFilter._private(reversed: true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [];
|
List<Object?> get props => [reversed];
|
||||||
|
|
||||||
const TrashFilter._private();
|
const TrashFilter._private({super.reversed = false});
|
||||||
|
|
||||||
|
factory TrashFilter.fromMap(Map<String, dynamic> json) {
|
||||||
|
final reversed = json['reversed'] ?? false;
|
||||||
|
return reversed ? instanceReversed : instance;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => (entry) => entry.trashed;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => type;
|
String get universalLabel => type;
|
||||||
|
|
|
@ -17,8 +17,8 @@ class TypeFilter extends CollectionFilter {
|
||||||
static const _sphericalVideo = 'spherical_video'; // subset of videos
|
static const _sphericalVideo = 'spherical_video'; // subset of videos
|
||||||
|
|
||||||
final String itemType;
|
final String itemType;
|
||||||
late final EntryFilter _test;
|
|
||||||
late final IconData _icon;
|
late final IconData _icon;
|
||||||
|
late final EntryFilter _test;
|
||||||
|
|
||||||
static final animated = TypeFilter._private(_animated);
|
static final animated = TypeFilter._private(_animated);
|
||||||
static final geotiff = TypeFilter._private(_geotiff);
|
static final geotiff = TypeFilter._private(_geotiff);
|
||||||
|
@ -28,9 +28,9 @@ class TypeFilter extends CollectionFilter {
|
||||||
static final sphericalVideo = TypeFilter._private(_sphericalVideo);
|
static final sphericalVideo = TypeFilter._private(_sphericalVideo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [itemType];
|
List<Object?> get props => [itemType, reversed];
|
||||||
|
|
||||||
TypeFilter._private(this.itemType) {
|
TypeFilter._private(this.itemType, {super.reversed = false}) {
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case _animated:
|
case _animated:
|
||||||
_test = (entry) => entry.isAnimated;
|
_test = (entry) => entry.isAnimated;
|
||||||
|
@ -62,6 +62,7 @@ class TypeFilter extends CollectionFilter {
|
||||||
factory TypeFilter.fromMap(Map<String, dynamic> json) {
|
factory TypeFilter.fromMap(Map<String, dynamic> json) {
|
||||||
return TypeFilter._private(
|
return TypeFilter._private(
|
||||||
json['itemType'],
|
json['itemType'],
|
||||||
|
reversed: json['reversed'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,10 +70,14 @@ class TypeFilter extends CollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'itemType': itemType,
|
'itemType': itemType,
|
||||||
|
'reversed': reversed,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EntryFilter get test => _test;
|
EntryFilter get positiveTest => _test;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get exclusiveProp => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get universalLabel => itemType;
|
String get universalLabel => itemType;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import 'package:aves/model/actions/entry_actions.dart';
|
import 'package:aves/model/actions/entry_actions.dart';
|
||||||
import 'package:aves/model/actions/entry_set_actions.dart';
|
import 'package:aves/model/actions/entry_set_actions.dart';
|
||||||
import 'package:aves/model/filters/favourite.dart';
|
|
||||||
import 'package:aves/model/filters/mime.dart';
|
|
||||||
import 'package:aves/model/filters/recent.dart';
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/naming_pattern.dart';
|
import 'package:aves/model/naming_pattern.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
|
@ -40,8 +38,6 @@ class SettingsDefaults {
|
||||||
static const setMetadataDateBeforeFileOp = false;
|
static const setMetadataDateBeforeFileOp = false;
|
||||||
static final drawerTypeBookmarks = [
|
static final drawerTypeBookmarks = [
|
||||||
null,
|
null,
|
||||||
MimeFilter.video,
|
|
||||||
FavouriteFilter.instance,
|
|
||||||
RecentlyAddedFilter.instance,
|
RecentlyAddedFilter.instance,
|
||||||
];
|
];
|
||||||
static const drawerPageBookmarks = [
|
static const drawerPageBookmarks = [
|
||||||
|
@ -93,7 +89,7 @@ class SettingsDefaults {
|
||||||
|
|
||||||
// video
|
// video
|
||||||
static const enableVideoHardwareAcceleration = true;
|
static const enableVideoHardwareAcceleration = true;
|
||||||
static const enableVideoAutoPlay = false;
|
static const videoAutoPlayMode = VideoAutoPlayMode.disabled;
|
||||||
static const videoLoopMode = VideoLoopMode.shortOnly;
|
static const videoLoopMode = VideoLoopMode.shortOnly;
|
||||||
static const videoShowRawTimedText = false;
|
static const videoShowRawTimedText = false;
|
||||||
static const videoControls = VideoControls.play;
|
static const videoControls = VideoControls.play;
|
||||||
|
@ -133,6 +129,7 @@ class SettingsDefaults {
|
||||||
static const slideshowRepeat = false;
|
static const slideshowRepeat = false;
|
||||||
static const slideshowShuffle = false;
|
static const slideshowShuffle = false;
|
||||||
static const slideshowFillScreen = false;
|
static const slideshowFillScreen = false;
|
||||||
|
static const slideshowAnimatedZoomEffect = true;
|
||||||
static const slideshowTransition = ViewerTransition.fade;
|
static const slideshowTransition = ViewerTransition.fade;
|
||||||
static const slideshowVideoPlayback = SlideshowVideoPlayback.playMuted;
|
static const slideshowVideoPlayback = SlideshowVideoPlayback.playMuted;
|
||||||
static const slideshowInterval = SlideshowInterval.s5;
|
static const slideshowInterval = SlideshowInterval.s5;
|
||||||
|
@ -140,6 +137,7 @@ class SettingsDefaults {
|
||||||
// widget
|
// widget
|
||||||
static const widgetOutline = false;
|
static const widgetOutline = false;
|
||||||
static const widgetShape = WidgetShape.rrect;
|
static const widgetShape = WidgetShape.rrect;
|
||||||
|
static const widgetOpenPage = WidgetOpenPage.viewer;
|
||||||
|
|
||||||
// platform settings
|
// platform settings
|
||||||
static const isRotationLocked = false;
|
static const isRotationLocked = false;
|
||||||
|
|
|
@ -28,6 +28,10 @@ enum VideoControls { play, playSeek, playOutside, none }
|
||||||
|
|
||||||
enum VideoLoopMode { never, shortOnly, always }
|
enum VideoLoopMode { never, shortOnly, always }
|
||||||
|
|
||||||
enum ViewerTransition { slide, parallax, fade, zoomIn }
|
enum VideoAutoPlayMode { disabled, playMuted, playWithSound }
|
||||||
|
|
||||||
|
enum ViewerTransition { slide, parallax, fade, zoomIn, none }
|
||||||
|
|
||||||
|
enum WidgetOpenPage { home, viewer }
|
||||||
|
|
||||||
enum WidgetShape { rrect, circle, heart }
|
enum WidgetShape { rrect, circle, heart }
|
|
@ -7,11 +7,11 @@ extension ExtraSlideshowVideoPlayback on SlideshowVideoPlayback {
|
||||||
String getName(BuildContext context) {
|
String getName(BuildContext context) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case SlideshowVideoPlayback.skip:
|
case SlideshowVideoPlayback.skip:
|
||||||
return context.l10n.slideshowVideoPlaybackSkip;
|
return context.l10n.videoPlaybackSkip;
|
||||||
case SlideshowVideoPlayback.playMuted:
|
case SlideshowVideoPlayback.playMuted:
|
||||||
return context.l10n.slideshowVideoPlaybackMuted;
|
return context.l10n.videoPlaybackMuted;
|
||||||
case SlideshowVideoPlayback.playWithSound:
|
case SlideshowVideoPlayback.playWithSound:
|
||||||
return context.l10n.slideshowVideoPlaybackWithSound;
|
return context.l10n.videoPlaybackWithSound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
lib/model/settings/enums/video_auto_play_mode.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'enums.dart';
|
||||||
|
|
||||||
|
extension ExtraSlideshowVideoPlayback on VideoAutoPlayMode {
|
||||||
|
String getName(BuildContext context) {
|
||||||
|
switch (this) {
|
||||||
|
case VideoAutoPlayMode.disabled:
|
||||||
|
return context.l10n.settingsDisabled;
|
||||||
|
case VideoAutoPlayMode.playMuted:
|
||||||
|
return context.l10n.videoPlaybackMuted;
|
||||||
|
case VideoAutoPlayMode.playWithSound:
|
||||||
|
return context.l10n.videoPlaybackWithSound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ extension ExtraViewerTransition on ViewerTransition {
|
||||||
return context.l10n.viewerTransitionFade;
|
return context.l10n.viewerTransitionFade;
|
||||||
case ViewerTransition.zoomIn:
|
case ViewerTransition.zoomIn:
|
||||||
return context.l10n.viewerTransitionZoomIn;
|
return context.l10n.viewerTransitionZoomIn;
|
||||||
|
case ViewerTransition.none:
|
||||||
|
return context.l10n.viewerTransitionNone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +30,8 @@ extension ExtraViewerTransition on ViewerTransition {
|
||||||
return PageTransitionEffects.fade(pageController, index, zoomIn: false);
|
return PageTransitionEffects.fade(pageController, index, zoomIn: false);
|
||||||
case ViewerTransition.zoomIn:
|
case ViewerTransition.zoomIn:
|
||||||
return PageTransitionEffects.fade(pageController, index, zoomIn: true);
|
return PageTransitionEffects.fade(pageController, index, zoomIn: true);
|
||||||
|
case ViewerTransition.none:
|
||||||
|
return PageTransitionEffects.none(pageController, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
lib/model/settings/enums/widget_open_action.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
extension ExtraWidgetOpenPage on WidgetOpenPage {
|
||||||
|
String getName(BuildContext context) {
|
||||||
|
switch (this) {
|
||||||
|
case WidgetOpenPage.home:
|
||||||
|
return context.l10n.widgetOpenPageHome;
|
||||||
|
case WidgetOpenPage.viewer:
|
||||||
|
return context.l10n.widgetOpenPageViewer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -112,7 +112,7 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
// video
|
// video
|
||||||
static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec';
|
static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec';
|
||||||
static const enableVideoAutoPlayKey = 'video_auto_play';
|
static const videoAutoPlayModeKey = 'video_auto_play_mode';
|
||||||
static const videoLoopModeKey = 'video_loop';
|
static const videoLoopModeKey = 'video_loop';
|
||||||
static const videoShowRawTimedTextKey = 'video_show_raw_timed_text';
|
static const videoShowRawTimedTextKey = 'video_show_raw_timed_text';
|
||||||
static const videoControlsKey = 'video_controls';
|
static const videoControlsKey = 'video_controls';
|
||||||
|
@ -151,6 +151,7 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
// screen saver
|
// screen saver
|
||||||
static const screenSaverFillScreenKey = 'screen_saver_fill_screen';
|
static const screenSaverFillScreenKey = 'screen_saver_fill_screen';
|
||||||
|
static const screenSaverAnimatedZoomEffectKey = 'screen_saver_animated_zoom_effect';
|
||||||
static const screenSaverTransitionKey = 'screen_saver_transition';
|
static const screenSaverTransitionKey = 'screen_saver_transition';
|
||||||
static const screenSaverVideoPlaybackKey = 'screen_saver_video_playback';
|
static const screenSaverVideoPlaybackKey = 'screen_saver_video_playback';
|
||||||
static const screenSaverIntervalKey = 'screen_saver_interval';
|
static const screenSaverIntervalKey = 'screen_saver_interval';
|
||||||
|
@ -160,6 +161,7 @@ class Settings extends ChangeNotifier {
|
||||||
static const slideshowRepeatKey = 'slideshow_loop';
|
static const slideshowRepeatKey = 'slideshow_loop';
|
||||||
static const slideshowShuffleKey = 'slideshow_shuffle';
|
static const slideshowShuffleKey = 'slideshow_shuffle';
|
||||||
static const slideshowFillScreenKey = 'slideshow_fill_screen';
|
static const slideshowFillScreenKey = 'slideshow_fill_screen';
|
||||||
|
static const slideshowAnimatedZoomEffectKey = 'slideshow_animated_zoom_effect';
|
||||||
static const slideshowTransitionKey = 'slideshow_transition';
|
static const slideshowTransitionKey = 'slideshow_transition';
|
||||||
static const slideshowVideoPlaybackKey = 'slideshow_video_playback';
|
static const slideshowVideoPlaybackKey = 'slideshow_video_playback';
|
||||||
static const slideshowIntervalKey = 'slideshow_interval';
|
static const slideshowIntervalKey = 'slideshow_interval';
|
||||||
|
@ -168,6 +170,7 @@ class Settings extends ChangeNotifier {
|
||||||
static const widgetOutlinePrefixKey = '${_widgetKeyPrefix}outline_';
|
static const widgetOutlinePrefixKey = '${_widgetKeyPrefix}outline_';
|
||||||
static const widgetShapePrefixKey = '${_widgetKeyPrefix}shape_';
|
static const widgetShapePrefixKey = '${_widgetKeyPrefix}shape_';
|
||||||
static const widgetCollectionFiltersPrefixKey = '${_widgetKeyPrefix}collection_filters_';
|
static const widgetCollectionFiltersPrefixKey = '${_widgetKeyPrefix}collection_filters_';
|
||||||
|
static const widgetOpenPagePrefixKey = '${_widgetKeyPrefix}open_page_';
|
||||||
static const widgetUriPrefixKey = '${_widgetKeyPrefix}uri_';
|
static const widgetUriPrefixKey = '${_widgetKeyPrefix}uri_';
|
||||||
|
|
||||||
// platform settings
|
// platform settings
|
||||||
|
@ -533,9 +536,9 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set enableVideoHardwareAcceleration(bool newValue) => setAndNotify(enableVideoHardwareAccelerationKey, newValue);
|
set enableVideoHardwareAcceleration(bool newValue) => setAndNotify(enableVideoHardwareAccelerationKey, newValue);
|
||||||
|
|
||||||
bool get enableVideoAutoPlay => getBoolOrDefault(enableVideoAutoPlayKey, SettingsDefaults.enableVideoAutoPlay);
|
VideoAutoPlayMode get videoAutoPlayMode => getEnumOrDefault(videoAutoPlayModeKey, SettingsDefaults.videoAutoPlayMode, VideoAutoPlayMode.values);
|
||||||
|
|
||||||
set enableVideoAutoPlay(bool newValue) => setAndNotify(enableVideoAutoPlayKey, newValue);
|
set videoAutoPlayMode(VideoAutoPlayMode newValue) => setAndNotify(videoAutoPlayModeKey, newValue.toString());
|
||||||
|
|
||||||
VideoLoopMode get videoLoopMode => getEnumOrDefault(videoLoopModeKey, SettingsDefaults.videoLoopMode, VideoLoopMode.values);
|
VideoLoopMode get videoLoopMode => getEnumOrDefault(videoLoopModeKey, SettingsDefaults.videoLoopMode, VideoLoopMode.values);
|
||||||
|
|
||||||
|
@ -648,6 +651,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set screenSaverFillScreen(bool newValue) => setAndNotify(screenSaverFillScreenKey, newValue);
|
set screenSaverFillScreen(bool newValue) => setAndNotify(screenSaverFillScreenKey, newValue);
|
||||||
|
|
||||||
|
bool get screenSaverAnimatedZoomEffect => getBoolOrDefault(screenSaverAnimatedZoomEffectKey, SettingsDefaults.slideshowAnimatedZoomEffect);
|
||||||
|
|
||||||
|
set screenSaverAnimatedZoomEffect(bool newValue) => setAndNotify(screenSaverAnimatedZoomEffectKey, newValue);
|
||||||
|
|
||||||
ViewerTransition get screenSaverTransition => getEnumOrDefault(screenSaverTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values);
|
ViewerTransition get screenSaverTransition => getEnumOrDefault(screenSaverTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values);
|
||||||
|
|
||||||
set screenSaverTransition(ViewerTransition newValue) => setAndNotify(screenSaverTransitionKey, newValue.toString());
|
set screenSaverTransition(ViewerTransition newValue) => setAndNotify(screenSaverTransitionKey, newValue.toString());
|
||||||
|
@ -678,6 +685,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set slideshowFillScreen(bool newValue) => setAndNotify(slideshowFillScreenKey, newValue);
|
set slideshowFillScreen(bool newValue) => setAndNotify(slideshowFillScreenKey, newValue);
|
||||||
|
|
||||||
|
bool get slideshowAnimatedZoomEffect => getBoolOrDefault(slideshowAnimatedZoomEffectKey, SettingsDefaults.slideshowAnimatedZoomEffect);
|
||||||
|
|
||||||
|
set slideshowAnimatedZoomEffect(bool newValue) => setAndNotify(slideshowAnimatedZoomEffectKey, newValue);
|
||||||
|
|
||||||
ViewerTransition get slideshowTransition => getEnumOrDefault(slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values);
|
ViewerTransition get slideshowTransition => getEnumOrDefault(slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values);
|
||||||
|
|
||||||
set slideshowTransition(ViewerTransition newValue) => setAndNotify(slideshowTransitionKey, newValue.toString());
|
set slideshowTransition(ViewerTransition newValue) => setAndNotify(slideshowTransitionKey, newValue.toString());
|
||||||
|
@ -707,6 +718,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
void setWidgetCollectionFilters(int widgetId, Set<CollectionFilter> newValue) => setAndNotify('$widgetCollectionFiltersPrefixKey$widgetId', newValue.map((filter) => filter.toJson()).toList());
|
void setWidgetCollectionFilters(int widgetId, Set<CollectionFilter> newValue) => setAndNotify('$widgetCollectionFiltersPrefixKey$widgetId', newValue.map((filter) => filter.toJson()).toList());
|
||||||
|
|
||||||
|
WidgetOpenPage getWidgetOpenPage(int widgetId) => getEnumOrDefault('$widgetOpenPagePrefixKey$widgetId', SettingsDefaults.widgetOpenPage, WidgetOpenPage.values);
|
||||||
|
|
||||||
|
void setWidgetOpenPage(int widgetId, WidgetOpenPage newValue) => setAndNotify('$widgetOpenPagePrefixKey$widgetId', newValue.toString());
|
||||||
|
|
||||||
String? getWidgetUri(int widgetId) => getString('$widgetUriPrefixKey$widgetId');
|
String? getWidgetUri(int widgetId) => getString('$widgetUriPrefixKey$widgetId');
|
||||||
|
|
||||||
void setWidgetUri(int widgetId, String? newValue) => setAndNotify('$widgetUriPrefixKey$widgetId', newValue);
|
void setWidgetUri(int widgetId, String? newValue) => setAndNotify('$widgetUriPrefixKey$widgetId', newValue);
|
||||||
|
@ -869,16 +884,17 @@ class Settings extends ChangeNotifier {
|
||||||
case viewerMaxBrightnessKey:
|
case viewerMaxBrightnessKey:
|
||||||
case enableMotionPhotoAutoPlayKey:
|
case enableMotionPhotoAutoPlayKey:
|
||||||
case enableVideoHardwareAccelerationKey:
|
case enableVideoHardwareAccelerationKey:
|
||||||
case enableVideoAutoPlayKey:
|
|
||||||
case videoGestureDoubleTapTogglePlayKey:
|
case videoGestureDoubleTapTogglePlayKey:
|
||||||
case videoGestureSideDoubleTapSeekKey:
|
case videoGestureSideDoubleTapSeekKey:
|
||||||
case subtitleShowOutlineKey:
|
case subtitleShowOutlineKey:
|
||||||
case saveSearchHistoryKey:
|
case saveSearchHistoryKey:
|
||||||
case filePickerShowHiddenFilesKey:
|
case filePickerShowHiddenFilesKey:
|
||||||
case screenSaverFillScreenKey:
|
case screenSaverFillScreenKey:
|
||||||
|
case screenSaverAnimatedZoomEffectKey:
|
||||||
case slideshowRepeatKey:
|
case slideshowRepeatKey:
|
||||||
case slideshowShuffleKey:
|
case slideshowShuffleKey:
|
||||||
case slideshowFillScreenKey:
|
case slideshowFillScreenKey:
|
||||||
|
case slideshowAnimatedZoomEffectKey:
|
||||||
if (newValue is bool) {
|
if (newValue is bool) {
|
||||||
settingsStore.setBool(key, newValue);
|
settingsStore.setBool(key, newValue);
|
||||||
} else {
|
} else {
|
||||||
|
@ -898,6 +914,7 @@ class Settings extends ChangeNotifier {
|
||||||
case countrySortFactorKey:
|
case countrySortFactorKey:
|
||||||
case tagSortFactorKey:
|
case tagSortFactorKey:
|
||||||
case imageBackgroundKey:
|
case imageBackgroundKey:
|
||||||
|
case videoAutoPlayModeKey:
|
||||||
case videoLoopModeKey:
|
case videoLoopModeKey:
|
||||||
case videoControlsKey:
|
case videoControlsKey:
|
||||||
case subtitleTextAlignmentKey:
|
case subtitleTextAlignmentKey:
|
||||||
|
|
|
@ -128,7 +128,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get showHeaders {
|
bool get showHeaders {
|
||||||
bool showAlbumHeaders() => !filters.any((f) => f is AlbumFilter);
|
bool showAlbumHeaders() => !filters.any((v) => v is AlbumFilter && !v.reversed);
|
||||||
|
|
||||||
switch (sortFactor) {
|
switch (sortFactor) {
|
||||||
case EntrySortFactor.date:
|
case EntrySortFactor.date:
|
||||||
|
|
|
@ -310,7 +310,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
||||||
contentId: newFields['contentId'] as int?,
|
contentId: newFields['contentId'] as int?,
|
||||||
// title can change when moved files are automatically renamed to avoid conflict
|
// title can change when moved files are automatically renamed to avoid conflict
|
||||||
title: newFields['title'] as String?,
|
title: newFields['title'] as String?,
|
||||||
dateAddedSecs: metadataDb.timestampSecs,
|
dateAddedSecs: newFields['dateAddedSecs'] as int?,
|
||||||
dateModifiedSecs: newFields['dateModifiedSecs'] as int?,
|
dateModifiedSecs: newFields['dateModifiedSecs'] as int?,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
|
@ -395,16 +395,25 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
||||||
Future<void> refreshEntry(AvesEntry entry, Set<EntryDataType> dataTypes) async {
|
Future<void> refreshEntry(AvesEntry entry, Set<EntryDataType> dataTypes) async {
|
||||||
await entry.refresh(background: false, persist: true, dataTypes: dataTypes, geocoderLocale: settings.appliedLocale);
|
await entry.refresh(background: false, persist: true, dataTypes: dataTypes, geocoderLocale: settings.appliedLocale);
|
||||||
|
|
||||||
// update/delete in DB
|
|
||||||
final id = entry.id;
|
final id = entry.id;
|
||||||
if (dataTypes.contains(EntryDataType.catalog)) {
|
await Future.forEach(EntryDataType.values, (dataType) async {
|
||||||
await metadataDb.updateCatalogMetadata(id, entry.catalogMetadata);
|
switch (dataType) {
|
||||||
onCatalogMetadataChanged();
|
case EntryDataType.aspectRatio:
|
||||||
}
|
onAspectRatioChanged();
|
||||||
if (dataTypes.contains(EntryDataType.address)) {
|
break;
|
||||||
await metadataDb.updateAddress(id, entry.addressDetails);
|
case EntryDataType.catalog:
|
||||||
onAddressMetadataChanged();
|
await metadataDb.updateCatalogMetadata(id, entry.catalogMetadata);
|
||||||
}
|
onCatalogMetadataChanged();
|
||||||
|
break;
|
||||||
|
case EntryDataType.address:
|
||||||
|
await metadataDb.updateAddress(id, entry.addressDetails);
|
||||||
|
onAddressMetadataChanged();
|
||||||
|
break;
|
||||||
|
case EntryDataType.basic:
|
||||||
|
case EntryDataType.references:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
updateDerivedFilters({entry});
|
updateDerivedFilters({entry});
|
||||||
eventBus.fire(EntryRefreshedEvent({entry}));
|
eventBus.fire(EntryRefreshedEvent({entry}));
|
||||||
|
@ -449,6 +458,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
||||||
state = SourceState.ready;
|
state = SourceState.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onAspectRatioChanged() => eventBus.fire(AspectRatioChangedEvent());
|
||||||
|
|
||||||
// monitoring
|
// monitoring
|
||||||
|
|
||||||
bool _monitoring = true;
|
bool _monitoring = true;
|
||||||
|
@ -502,3 +513,5 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AspectRatioChangedEvent {}
|
||||||
|
|
|
@ -2,10 +2,10 @@ enum SourceState { loading, cataloguing, locatingCountries, locatingPlaces, read
|
||||||
|
|
||||||
enum ChipSortFactor { date, name, count, size }
|
enum ChipSortFactor { date, name, count, size }
|
||||||
|
|
||||||
enum AlbumChipGroupFactor { none, importance, volume }
|
enum AlbumChipGroupFactor { none, importance, mimeType, volume }
|
||||||
|
|
||||||
enum EntrySortFactor { date, name, rating, size }
|
enum EntrySortFactor { date, name, rating, size }
|
||||||
|
|
||||||
enum EntryGroupFactor { none, album, month, day }
|
enum EntryGroupFactor { none, album, month, day }
|
||||||
|
|
||||||
enum TileLayout { grid, list }
|
enum TileLayout { mosaic, grid, list }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
@ -18,6 +19,19 @@ extension ExtraEntrySortFactor on EntrySortFactor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData get icon {
|
||||||
|
switch (this) {
|
||||||
|
case EntrySortFactor.date:
|
||||||
|
return AIcons.date;
|
||||||
|
case EntrySortFactor.name:
|
||||||
|
return AIcons.name;
|
||||||
|
case EntrySortFactor.rating:
|
||||||
|
return AIcons.rating;
|
||||||
|
case EntrySortFactor.size:
|
||||||
|
return AIcons.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String getOrderName(BuildContext context, bool reverse) {
|
String getOrderName(BuildContext context, bool reverse) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
@ -48,6 +62,19 @@ extension ExtraChipSortFactor on ChipSortFactor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData get icon {
|
||||||
|
switch (this) {
|
||||||
|
case ChipSortFactor.date:
|
||||||
|
return AIcons.date;
|
||||||
|
case ChipSortFactor.name:
|
||||||
|
return AIcons.name;
|
||||||
|
case ChipSortFactor.count:
|
||||||
|
return AIcons.count;
|
||||||
|
case ChipSortFactor.size:
|
||||||
|
return AIcons.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String getOrderName(BuildContext context, bool reverse) {
|
String getOrderName(BuildContext context, bool reverse) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
@ -76,6 +103,19 @@ extension ExtraEntryGroupFactor on EntryGroupFactor {
|
||||||
return l10n.collectionGroupNone;
|
return l10n.collectionGroupNone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData get icon {
|
||||||
|
switch (this) {
|
||||||
|
case EntryGroupFactor.album:
|
||||||
|
return AIcons.album;
|
||||||
|
case EntryGroupFactor.month:
|
||||||
|
return AIcons.dateByMonth;
|
||||||
|
case EntryGroupFactor.day:
|
||||||
|
return AIcons.dateByDay;
|
||||||
|
case EntryGroupFactor.none:
|
||||||
|
return AIcons.clear;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExtraAlbumChipGroupFactor on AlbumChipGroupFactor {
|
extension ExtraAlbumChipGroupFactor on AlbumChipGroupFactor {
|
||||||
|
@ -84,22 +124,50 @@ extension ExtraAlbumChipGroupFactor on AlbumChipGroupFactor {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case AlbumChipGroupFactor.importance:
|
case AlbumChipGroupFactor.importance:
|
||||||
return l10n.albumGroupTier;
|
return l10n.albumGroupTier;
|
||||||
|
case AlbumChipGroupFactor.mimeType:
|
||||||
|
return l10n.albumGroupType;
|
||||||
case AlbumChipGroupFactor.volume:
|
case AlbumChipGroupFactor.volume:
|
||||||
return l10n.albumGroupVolume;
|
return l10n.albumGroupVolume;
|
||||||
case AlbumChipGroupFactor.none:
|
case AlbumChipGroupFactor.none:
|
||||||
return l10n.albumGroupNone;
|
return l10n.albumGroupNone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData get icon {
|
||||||
|
switch (this) {
|
||||||
|
case AlbumChipGroupFactor.importance:
|
||||||
|
return AIcons.important;
|
||||||
|
case AlbumChipGroupFactor.mimeType:
|
||||||
|
return AIcons.mimeType;
|
||||||
|
case AlbumChipGroupFactor.volume:
|
||||||
|
return AIcons.removableStorage;
|
||||||
|
case AlbumChipGroupFactor.none:
|
||||||
|
return AIcons.clear;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExtraTileLayout on TileLayout {
|
extension ExtraTileLayout on TileLayout {
|
||||||
String getName(BuildContext context) {
|
String getName(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
case TileLayout.mosaic:
|
||||||
|
return l10n.tileLayoutMosaic;
|
||||||
case TileLayout.grid:
|
case TileLayout.grid:
|
||||||
return l10n.tileLayoutGrid;
|
return l10n.tileLayoutGrid;
|
||||||
case TileLayout.list:
|
case TileLayout.list:
|
||||||
return l10n.tileLayoutList;
|
return l10n.tileLayoutList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData get icon {
|
||||||
|
switch (this) {
|
||||||
|
case TileLayout.mosaic:
|
||||||
|
return AIcons.layoutMosaic;
|
||||||
|
case TileLayout.grid:
|
||||||
|
return AIcons.layoutGrid;
|
||||||
|
case TileLayout.list:
|
||||||
|
return AIcons.layoutList;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,13 +159,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
||||||
final contentId = entry.contentId;
|
final contentId = entry.contentId;
|
||||||
final existingEntry = knownContentIds.contains(contentId) ? knownLiveEntries.firstWhereOrNull((entry) => entry.contentId == contentId) : null;
|
final existingEntry = knownContentIds.contains(contentId) ? knownLiveEntries.firstWhereOrNull((entry) => entry.contentId == contentId) : null;
|
||||||
if (existingEntry != null) {
|
entry.id = existingEntry?.id ?? metadataDb.nextId;
|
||||||
entry.id = existingEntry.id;
|
|
||||||
entry.dateAddedSecs = existingEntry.dateAddedSecs;
|
|
||||||
} else {
|
|
||||||
entry.id = metadataDb.nextId;
|
|
||||||
entry.dateAddedSecs = metadataDb.timestampSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingNewEntries.add(entry);
|
pendingNewEntries.add(entry);
|
||||||
if (pendingNewEntries.length >= refreshCount) {
|
if (pendingNewEntries.length >= refreshCount) {
|
||||||
|
@ -250,13 +244,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
final newPath = sourceEntry.path;
|
final newPath = sourceEntry.path;
|
||||||
final volume = newPath != null ? androidFileUtils.getStorageVolume(newPath) : null;
|
final volume = newPath != null ? androidFileUtils.getStorageVolume(newPath) : null;
|
||||||
if (volume != null) {
|
if (volume != null) {
|
||||||
if (existingEntry != null) {
|
sourceEntry.id = existingEntry?.id ?? metadataDb.nextId;
|
||||||
sourceEntry.id = existingEntry.id;
|
|
||||||
sourceEntry.dateAddedSecs = existingEntry.dateAddedSecs;
|
|
||||||
} else {
|
|
||||||
sourceEntry.id = metadataDb.nextId;
|
|
||||||
sourceEntry.dateAddedSecs = metadataDb.timestampSecs;
|
|
||||||
}
|
|
||||||
newEntries.add(sourceEntry);
|
newEntries.add(sourceEntry);
|
||||||
final existingDirectory = existingEntry?.directory;
|
final existingDirectory = existingEntry?.directory;
|
||||||
if (existingDirectory != null) {
|
if (existingDirectory != null) {
|
||||||
|
|
|
@ -140,7 +140,7 @@ class VideoMetadataFormatter {
|
||||||
hour = int.tryParse(match.group(5)!) ?? 0;
|
hour = int.tryParse(match.group(5)!) ?? 0;
|
||||||
minute = int.tryParse(match.group(6)!) ?? 0;
|
minute = int.tryParse(match.group(6)!) ?? 0;
|
||||||
second = int.tryParse(match.group(7)!) ?? 0;
|
second = int.tryParse(match.group(7)!) ?? 0;
|
||||||
pm = match.group(9) == 'pm';
|
pm = {'pm', 'p. m.'}.contains(match.group(9));
|
||||||
}
|
}
|
||||||
|
|
||||||
final date = DateTime(year, month, day, hour + (pm ? 12 : 0), minute, second, 0);
|
final date = DateTime(year, month, day, hour + (pm ? 12 : 0), minute, second, 0);
|
||||||
|
|
|
@ -81,9 +81,8 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
}) as Map;
|
}) as Map;
|
||||||
return AvesEntry.fromMap(result);
|
return AvesEntry.fromMap(result);
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
// do not report issues with simple parameter-less media content
|
// do not report issues with media content as it is likely an obsolete Media Store entry
|
||||||
// as it is likely an obsolete Media Store entry
|
if (!uri.startsWith('content://media/')) {
|
||||||
if (!uri.startsWith('content://media/') || uri.contains('?')) {
|
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,12 @@ class Durations {
|
||||||
static const chipDecorationAnimation = Duration(milliseconds: 200);
|
static const chipDecorationAnimation = Duration(milliseconds: 200);
|
||||||
static const highlightScrollAnimationMinMillis = 400;
|
static const highlightScrollAnimationMinMillis = 400;
|
||||||
static const highlightScrollAnimationMaxMillis = 2000;
|
static const highlightScrollAnimationMaxMillis = 2000;
|
||||||
|
static const scalingGridBackgroundAnimation = Duration(milliseconds: 200);
|
||||||
|
static const scalingGridPositionAnimation = Duration(milliseconds: 150);
|
||||||
|
|
||||||
// collection animations
|
// collection animations
|
||||||
static const filterBarRemovalAnimation = Duration(milliseconds: 400);
|
static const filterBarRemovalAnimation = Duration(milliseconds: 400);
|
||||||
static const collectionOpOverlayAnimation = Duration(milliseconds: 300);
|
static const collectionOpOverlayAnimation = Duration(milliseconds: 300);
|
||||||
static const collectionScalingBackgroundAnimation = Duration(milliseconds: 200);
|
|
||||||
static const sectionHeaderAnimation = Duration(milliseconds: 200);
|
static const sectionHeaderAnimation = Duration(milliseconds: 200);
|
||||||
static const thumbnailOverlayAnimation = Duration(milliseconds: 200);
|
static const thumbnailOverlayAnimation = Duration(milliseconds: 200);
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ class Durations {
|
||||||
static const thumbnailScrollerShadeAnimation = Duration(milliseconds: 150);
|
static const thumbnailScrollerShadeAnimation = Duration(milliseconds: 150);
|
||||||
static const viewerVideoPlayerTransition = Duration(milliseconds: 500);
|
static const viewerVideoPlayerTransition = Duration(milliseconds: 500);
|
||||||
static const viewerActionFeedbackAnimation = Duration(milliseconds: 600);
|
static const viewerActionFeedbackAnimation = Duration(milliseconds: 600);
|
||||||
|
static const viewerHorizontalPageAnimation = Duration(seconds: 1);
|
||||||
|
|
||||||
// info animations
|
// info animations
|
||||||
static const mapStyleSwitchAnimation = Duration(milliseconds: 300);
|
static const mapStyleSwitchAnimation = Duration(milliseconds: 300);
|
||||||
|
@ -95,6 +97,7 @@ class DurationsData {
|
||||||
// common animations
|
// common animations
|
||||||
final Duration expansionTileAnimation;
|
final Duration expansionTileAnimation;
|
||||||
final Duration formTransition;
|
final Duration formTransition;
|
||||||
|
final Duration formTextStyleTransition;
|
||||||
final Duration chartTransition;
|
final Duration chartTransition;
|
||||||
final Duration iconAnimation;
|
final Duration iconAnimation;
|
||||||
final Duration staggeredAnimation;
|
final Duration staggeredAnimation;
|
||||||
|
@ -111,6 +114,7 @@ class DurationsData {
|
||||||
const DurationsData({
|
const DurationsData({
|
||||||
this.expansionTileAnimation = const Duration(milliseconds: 200),
|
this.expansionTileAnimation = const Duration(milliseconds: 200),
|
||||||
this.formTransition = const Duration(milliseconds: 200),
|
this.formTransition = const Duration(milliseconds: 200),
|
||||||
|
this.formTextStyleTransition = const Duration(milliseconds: 800),
|
||||||
this.chartTransition = const Duration(milliseconds: 400),
|
this.chartTransition = const Duration(milliseconds: 400),
|
||||||
this.iconAnimation = const Duration(milliseconds: 300),
|
this.iconAnimation = const Duration(milliseconds: 300),
|
||||||
this.staggeredAnimation = const Duration(milliseconds: 375),
|
this.staggeredAnimation = const Duration(milliseconds: 375),
|
||||||
|
@ -125,6 +129,7 @@ class DurationsData {
|
||||||
// as of Flutter v2.5.1, `ExpansionPanelList` throws if animation duration is zero
|
// as of Flutter v2.5.1, `ExpansionPanelList` throws if animation duration is zero
|
||||||
expansionTileAnimation: const Duration(microseconds: 1),
|
expansionTileAnimation: const Duration(microseconds: 1),
|
||||||
formTransition: Duration.zero,
|
formTransition: Duration.zero,
|
||||||
|
formTextStyleTransition: Duration.zero,
|
||||||
chartTransition: Duration.zero,
|
chartTransition: Duration.zero,
|
||||||
iconAnimation: Duration.zero,
|
iconAnimation: Duration.zero,
|
||||||
staggeredAnimation: Duration.zero,
|
staggeredAnimation: Duration.zero,
|
||||||
|
|
|
@ -14,8 +14,11 @@ class AIcons {
|
||||||
static const IconData bin = Icons.delete_outlined;
|
static const IconData bin = Icons.delete_outlined;
|
||||||
static const IconData broken = Icons.broken_image_outlined;
|
static const IconData broken = Icons.broken_image_outlined;
|
||||||
static const IconData checked = Icons.done_outlined;
|
static const IconData checked = Icons.done_outlined;
|
||||||
|
static const IconData count = MdiIcons.counter;
|
||||||
static const IconData counter = Icons.plus_one_outlined;
|
static const IconData counter = Icons.plus_one_outlined;
|
||||||
static const IconData date = Icons.calendar_today_outlined;
|
static const IconData date = Icons.calendar_today_outlined;
|
||||||
|
static const IconData dateByDay = Icons.today_outlined;
|
||||||
|
static const IconData dateByMonth = Icons.calendar_month_outlined;
|
||||||
static const IconData dateRecent = Icons.today_outlined;
|
static const IconData dateRecent = Icons.today_outlined;
|
||||||
static const IconData dateUndated = Icons.event_busy_outlined;
|
static const IconData dateUndated = Icons.event_busy_outlined;
|
||||||
static const IconData description = Icons.description_outlined;
|
static const IconData description = Icons.description_outlined;
|
||||||
|
@ -31,6 +34,7 @@ class AIcons {
|
||||||
static const IconData location = Icons.place_outlined;
|
static const IconData location = Icons.place_outlined;
|
||||||
static const IconData locationUnlocated = Icons.location_off_outlined;
|
static const IconData locationUnlocated = Icons.location_off_outlined;
|
||||||
static const IconData mainStorage = Icons.smartphone_outlined;
|
static const IconData mainStorage = Icons.smartphone_outlined;
|
||||||
|
static const IconData mimeType = Icons.code_outlined;
|
||||||
static const IconData opacity = Icons.opacity;
|
static const IconData opacity = Icons.opacity;
|
||||||
static const IconData privacy = MdiIcons.shieldAccountOutline;
|
static const IconData privacy = MdiIcons.shieldAccountOutline;
|
||||||
static const IconData rating = Icons.star_border_outlined;
|
static const IconData rating = Icons.star_border_outlined;
|
||||||
|
@ -43,6 +47,7 @@ class AIcons {
|
||||||
static const IconData sensorControlEnabled = Icons.explore_outlined;
|
static const IconData sensorControlEnabled = Icons.explore_outlined;
|
||||||
static const IconData sensorControlDisabled = Icons.explore_off_outlined;
|
static const IconData sensorControlDisabled = Icons.explore_off_outlined;
|
||||||
static const IconData settings = Icons.settings_outlined;
|
static const IconData settings = Icons.settings_outlined;
|
||||||
|
static const IconData size = Icons.data_usage_outlined;
|
||||||
static const IconData text = Icons.format_quote_outlined;
|
static const IconData text = Icons.format_quote_outlined;
|
||||||
static const IconData tag = Icons.local_offer_outlined;
|
static const IconData tag = Icons.local_offer_outlined;
|
||||||
static const IconData tagUntagged = MdiIcons.tagOffOutline;
|
static const IconData tagUntagged = MdiIcons.tagOffOutline;
|
||||||
|
@ -50,6 +55,9 @@ class AIcons {
|
||||||
// view
|
// view
|
||||||
static const IconData group = Icons.group_work_outlined;
|
static const IconData group = Icons.group_work_outlined;
|
||||||
static const IconData layout = Icons.grid_view_outlined;
|
static const IconData layout = Icons.grid_view_outlined;
|
||||||
|
static const IconData layoutMosaic = Icons.view_compact_outlined;
|
||||||
|
static const IconData layoutGrid = Icons.view_comfy_outlined;
|
||||||
|
static const IconData layoutList = Icons.list_outlined;
|
||||||
static const IconData sort = Icons.sort_outlined;
|
static const IconData sort = Icons.sort_outlined;
|
||||||
static const IconData sortOrder = Icons.swap_vert_outlined;
|
static const IconData sortOrder = Icons.swap_vert_outlined;
|
||||||
|
|
||||||
|
@ -97,6 +105,7 @@ class AIcons {
|
||||||
static const IconData print = Icons.print_outlined;
|
static const IconData print = Icons.print_outlined;
|
||||||
static const IconData refresh = Icons.refresh_outlined;
|
static const IconData refresh = Icons.refresh_outlined;
|
||||||
static const IconData replay10 = Icons.replay_10_outlined;
|
static const IconData replay10 = Icons.replay_10_outlined;
|
||||||
|
static const IconData reverse = Icons.invert_colors_outlined;
|
||||||
static const IconData skip10 = Icons.forward_10_outlined;
|
static const IconData skip10 = Icons.forward_10_outlined;
|
||||||
static const IconData reset = Icons.restart_alt_outlined;
|
static const IconData reset = Icons.restart_alt_outlined;
|
||||||
static const IconData restore = Icons.restore_outlined;
|
static const IconData restore = Icons.restore_outlined;
|
||||||
|
@ -111,7 +120,7 @@ class AIcons {
|
||||||
static const IconData show = Icons.visibility_outlined;
|
static const IconData show = Icons.visibility_outlined;
|
||||||
static const IconData slideshow = Icons.slideshow_outlined;
|
static const IconData slideshow = Icons.slideshow_outlined;
|
||||||
static const IconData speed = Icons.speed_outlined;
|
static const IconData speed = Icons.speed_outlined;
|
||||||
static const IconData stats = Icons.pie_chart_outline_outlined;
|
static const IconData stats = Icons.donut_small_outlined;
|
||||||
static const IconData streams = Icons.translate_outlined;
|
static const IconData streams = Icons.translate_outlined;
|
||||||
static const IconData streamVideo = Icons.movie_outlined;
|
static const IconData streamVideo = Icons.movie_outlined;
|
||||||
static const IconData streamAudio = Icons.audiotrack_outlined;
|
static const IconData streamAudio = Icons.audiotrack_outlined;
|
||||||
|
|
|
@ -5,6 +5,11 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
class Constants {
|
class Constants {
|
||||||
|
// `Color(0x00FFFFFF)` is different from `Color(0x00000000)` (or `Colors.transparent`)
|
||||||
|
// when used in gradients or lerping to it
|
||||||
|
static const transparentWhite = Color(0x00FFFFFF);
|
||||||
|
static const transparentBlack = Colors.transparent;
|
||||||
|
|
||||||
// as of Flutter v2.8.0, overflowing `Text` miscalculates height and some text (e.g. 'Å') is clipped
|
// as of Flutter v2.8.0, overflowing `Text` miscalculates height and some text (e.g. 'Å') is clipped
|
||||||
// so we give it a `strutStyle` with a slightly larger height
|
// so we give it a `strutStyle` with a slightly larger height
|
||||||
static const overflowStrutStyle = StrutStyle(height: 1.3);
|
static const overflowStrutStyle = StrutStyle(height: 1.3);
|
||||||
|
@ -307,6 +312,11 @@ class Constants {
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
sourceUrl: 'https://github.com/rrousselGit/provider',
|
sourceUrl: 'https://github.com/rrousselGit/provider',
|
||||||
),
|
),
|
||||||
|
Dependency(
|
||||||
|
name: 'Smooth Page Indicator',
|
||||||
|
license: 'MIT',
|
||||||
|
sourceUrl: 'https://github.com/Milad-Akarie/smooth_page_indicator',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
static const List<Dependency> dartPackages = [
|
static const List<Dependency> dartPackages = [
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Namespaces {
|
||||||
static const container = 'http://ns.google.com/photos/1.0/container/';
|
static const container = 'http://ns.google.com/photos/1.0/container/';
|
||||||
static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/';
|
static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/';
|
||||||
static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/';
|
static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/';
|
||||||
|
static const crlcp = 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/';
|
||||||
static const crs = 'http://ns.adobe.com/camera-raw-settings/1.0/';
|
static const crs = 'http://ns.adobe.com/camera-raw-settings/1.0/';
|
||||||
static const crss = 'http://ns.adobe.com/camera-raw-saved-settings/1.0/';
|
static const crss = 'http://ns.adobe.com/camera-raw-saved-settings/1.0/';
|
||||||
static const darktable = 'http://darktable.sf.net/';
|
static const darktable = 'http://darktable.sf.net/';
|
||||||
|
@ -30,7 +31,8 @@ class Namespaces {
|
||||||
static const gettyImagesGift = 'http://xmp.gettyimages.com/gift/1.0/';
|
static const gettyImagesGift = 'http://xmp.gettyimages.com/gift/1.0/';
|
||||||
static const gFocus = 'http://ns.google.com/photos/1.0/focus/';
|
static const gFocus = 'http://ns.google.com/photos/1.0/focus/';
|
||||||
static const gImage = 'http://ns.google.com/photos/1.0/image/';
|
static const gImage = 'http://ns.google.com/photos/1.0/image/';
|
||||||
static const gimp = 'http://www.gimp.org/ns/2.10/';
|
static const gimp210 = 'http://www.gimp.org/ns/2.10/';
|
||||||
|
static const gimpXmp = 'http://www.gimp.org/xmp/';
|
||||||
static const gPano = 'http://ns.google.com/photos/1.0/panorama/';
|
static const gPano = 'http://ns.google.com/photos/1.0/panorama/';
|
||||||
static const gSpherical = 'http://ns.google.com/videos/1.0/spherical/';
|
static const gSpherical = 'http://ns.google.com/videos/1.0/spherical/';
|
||||||
static const illustrator = 'http://ns.adobe.com/illustrator/1.0/';
|
static const illustrator = 'http://ns.adobe.com/illustrator/1.0/';
|
||||||
|
@ -56,6 +58,7 @@ class Namespaces {
|
||||||
static const plus = 'http://ns.useplus.org/ldf/xmp/1.0/';
|
static const plus = 'http://ns.useplus.org/ldf/xmp/1.0/';
|
||||||
static const pmtm = 'http://www.hdrsoft.com/photomatix_settings01';
|
static const pmtm = 'http://www.hdrsoft.com/photomatix_settings01';
|
||||||
static const rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
static const rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
|
||||||
|
static const stCamera = 'http://ns.adobe.com/photoshop/1.0/camera-profile';
|
||||||
static const stEvt = 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#';
|
static const stEvt = 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#';
|
||||||
static const stRef = 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#';
|
static const stRef = 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#';
|
||||||
static const tiff = 'http://ns.adobe.com/tiff/1.0/';
|
static const tiff = 'http://ns.adobe.com/tiff/1.0/';
|
||||||
|
@ -96,7 +99,8 @@ class Namespaces {
|
||||||
gDepth: 'Google Depth',
|
gDepth: 'Google Depth',
|
||||||
gFocus: 'Google Focus',
|
gFocus: 'Google Focus',
|
||||||
gImage: 'Google Image',
|
gImage: 'Google Image',
|
||||||
gimp: 'GIMP',
|
gimp210: 'GIMP 2.10',
|
||||||
|
gimpXmp: 'GIMP',
|
||||||
gPano: 'Google Panorama',
|
gPano: 'Google Panorama',
|
||||||
gSpherical: 'Google Spherical',
|
gSpherical: 'Google Spherical',
|
||||||
illustrator: 'Illustrator',
|
illustrator: 'Illustrator',
|
||||||
|
@ -122,6 +126,7 @@ class Namespaces {
|
||||||
xmpBJ: 'Basic Job Ticket',
|
xmpBJ: 'Basic Job Ticket',
|
||||||
xmpDM: 'Dynamic Media',
|
xmpDM: 'Dynamic Media',
|
||||||
xmpMM: 'Media Management',
|
xmpMM: 'Media Management',
|
||||||
|
xmpNote: 'Note',
|
||||||
xmpRights: 'Rights Management',
|
xmpRights: 'Rights Management',
|
||||||
xmpTPg: 'Paged-Text',
|
xmpTPg: 'Paged-Text',
|
||||||
};
|
};
|
||||||
|
|
|
@ -128,7 +128,7 @@ class LicenseRow extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final bodyTextStyle = textTheme.bodyText2!;
|
final bodyTextStyle = textTheme.bodyMedium!;
|
||||||
final subColor = bodyTextStyle.color!.withOpacity(.6);
|
final subColor = bodyTextStyle.color!.withOpacity(.6);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
|
@ -105,11 +105,11 @@ class AvesApp extends StatefulWidget {
|
||||||
class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
|
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
|
||||||
late final Future<void> _appSetup;
|
late final Future<void> _appSetup;
|
||||||
late final Size _screenSize;
|
|
||||||
late final Future<CorePalette?> _dynamicColorPaletteLoader;
|
late final Future<CorePalette?> _dynamicColorPaletteLoader;
|
||||||
final CollectionSource _mediaStoreSource = MediaStoreSource();
|
final CollectionSource _mediaStoreSource = MediaStoreSource();
|
||||||
final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay);
|
final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay);
|
||||||
final Set<String> _changedUris = {};
|
final Set<String> _changedUris = {};
|
||||||
|
Size? _screenSize;
|
||||||
|
|
||||||
// observers are not registered when using the same list object with different items
|
// observers are not registered when using the same list object with different items
|
||||||
// the list itself needs to be reassigned
|
// the list itself needs to be reassigned
|
||||||
|
@ -119,15 +119,13 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
final EventChannel _analysisCompletionChannel = const OptionalEventChannel('deckers.thibault/aves/analysis_events');
|
final EventChannel _analysisCompletionChannel = const OptionalEventChannel('deckers.thibault/aves/analysis_events');
|
||||||
final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error');
|
final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error');
|
||||||
|
|
||||||
Widget getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
EquatableConfig.stringify = true;
|
EquatableConfig.stringify = true;
|
||||||
_appSetup = _setup();
|
_appSetup = _setup();
|
||||||
// remember screen size to use it later, when `context` and `window` are no longer reliable
|
// remember screen size to use it later, when `context` and `window` are no longer reliable
|
||||||
_screenSize = window.physicalSize / window.devicePixelRatio;
|
_screenSize = _getScreenSize();
|
||||||
_dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette();
|
_dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette();
|
||||||
_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChange(event as String?));
|
_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChange(event as String?));
|
||||||
_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?));
|
_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?));
|
||||||
|
@ -159,7 +157,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
AvesApp.showSystemUI();
|
AvesApp.showSystemUI();
|
||||||
}
|
}
|
||||||
final home = initialized
|
final home = initialized
|
||||||
? getFirstPage()
|
? _getFirstPage()
|
||||||
: Scaffold(
|
: Scaffold(
|
||||||
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
|
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
|
||||||
);
|
);
|
||||||
|
@ -291,23 +289,31 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage();
|
||||||
|
|
||||||
|
Size? _getScreenSize() {
|
||||||
|
final physicalSize = window.physicalSize;
|
||||||
|
final ratio = window.devicePixelRatio;
|
||||||
|
return physicalSize > Size.zero && ratio > 0 ? physicalSize / ratio : null;
|
||||||
|
}
|
||||||
|
|
||||||
// save IDs of entries visible at the top of the collection page with current layout settings
|
// save IDs of entries visible at the top of the collection page with current layout settings
|
||||||
void _saveTopEntries() {
|
void _saveTopEntries() {
|
||||||
if (!settings.initialized) return;
|
if (!settings.initialized) return;
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final screenSize = _screenSize ?? _getScreenSize();
|
||||||
|
if (screenSize == null) return;
|
||||||
|
|
||||||
var tileExtent = settings.getTileExtent(CollectionPage.routeName);
|
var tileExtent = settings.getTileExtent(CollectionPage.routeName);
|
||||||
if (tileExtent == 0) {
|
if (tileExtent == 0) {
|
||||||
tileExtent = _screenSize.shortestSide / CollectionGrid.columnCountDefault;
|
tileExtent = screenSize.shortestSide / CollectionGrid.columnCountDefault;
|
||||||
}
|
}
|
||||||
final rows = (_screenSize.height / tileExtent).ceil();
|
final rows = (screenSize.height / tileExtent).ceil();
|
||||||
final columns = (_screenSize.width / tileExtent).ceil();
|
final columns = (screenSize.width / tileExtent).ceil();
|
||||||
final count = rows * columns;
|
final count = rows * columns;
|
||||||
final collection = CollectionLens(source: _mediaStoreSource, listenToSource: false);
|
final collection = CollectionLens(source: _mediaStoreSource, listenToSource: false);
|
||||||
settings.topEntryIds = collection.sortedEntries.take(count).map((entry) => entry.id).toList();
|
settings.topEntryIds = collection.sortedEntries.take(count).map((entry) => entry.id).toList();
|
||||||
collection.dispose();
|
collection.dispose();
|
||||||
debugPrint('Saved $count top entries in ${stopwatch.elapsed.inMilliseconds}ms');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup before the first page is displayed. keep it short
|
// setup before the first page is displayed. keep it short
|
||||||
|
@ -374,10 +380,10 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
'locales': WidgetsBinding.instance.window.locales.join(', '),
|
'locales': WidgetsBinding.instance.window.locales.join(', '),
|
||||||
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
|
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
|
||||||
});
|
});
|
||||||
_navigatorObservers = [
|
setState(() => _navigatorObservers = [
|
||||||
AvesApp.pageRouteObserver,
|
AvesApp.pageRouteObserver,
|
||||||
ReportingRouteTracker(),
|
ReportingRouteTracker(),
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onNewIntent(Map? intentData) {
|
void _onNewIntent(Map? intentData) {
|
||||||
|
@ -389,7 +395,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
reportService.log('New intent');
|
reportService.log('New intent');
|
||||||
AvesApp.navigatorKey.currentState!.pushReplacement(DirectMaterialPageRoute(
|
AvesApp.navigatorKey.currentState!.pushReplacement(DirectMaterialPageRoute(
|
||||||
settings: const RouteSettings(name: HomePage.routeName),
|
settings: const RouteSettings(name: HomePage.routeName),
|
||||||
builder: (_) => getFirstPage(intentData: intentData),
|
builder: (_) => _getFirstPage(intentData: intentData),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_app_bar.dart';
|
import 'package:aves/widgets/common/identity/aves_app_bar.dart';
|
||||||
import 'package:aves/widgets/common/search/route.dart';
|
import 'package:aves/widgets/common/search/route.dart';
|
||||||
import 'package:aves/widgets/dialogs/tile_view_dialog.dart';
|
import 'package:aves/widgets/dialogs/tile_view_dialog.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
||||||
import 'package:aves/widgets/search/search_delegate.dart';
|
import 'package:aves/widgets/search/search_delegate.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
@ -83,6 +84,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
];
|
];
|
||||||
|
|
||||||
static const _layoutOptions = [
|
static const _layoutOptions = [
|
||||||
|
TileLayout.mosaic,
|
||||||
TileLayout.grid,
|
TileLayout.grid,
|
||||||
TileLayout.list,
|
TileLayout.list,
|
||||||
];
|
];
|
||||||
|
@ -167,10 +169,16 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
bottom: Column(
|
bottom: Column(
|
||||||
children: [
|
children: [
|
||||||
if (showFilterBar)
|
if (showFilterBar)
|
||||||
FilterBar(
|
NotificationListener<ReverseFilterNotification>(
|
||||||
filters: visibleFilters,
|
onNotification: (notification) {
|
||||||
removable: removableFilters,
|
collection.addFilter(notification.reversedFilter);
|
||||||
onTap: removableFilters ? collection.removeFilter : null,
|
return true;
|
||||||
|
},
|
||||||
|
child: FilterBar(
|
||||||
|
filters: visibleFilters,
|
||||||
|
removable: removableFilters,
|
||||||
|
onTap: removableFilters ? collection.removeFilter : null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (queryEnabled)
|
if (queryEnabled)
|
||||||
EntryQueryBar(
|
EntryQueryBar(
|
||||||
|
@ -310,7 +318,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
title: context.l10n.collectionActionEdit,
|
title: context.l10n.collectionActionEdit,
|
||||||
items: [
|
items: [
|
||||||
_buildRotateAndFlipMenuItems(context, canApply: canApply),
|
_buildRotateAndFlipMenuItems(context, canApply: canApply),
|
||||||
...EntrySetActions.edit.where(isVisible).map((action) => _toMenuItem(action, enabled: canApply(action), selection: selection)),
|
...EntrySetActions.edit.where((v) => isVisible(v) && !selectionQuickActions.contains(v)).map((action) => _toMenuItem(action, enabled: canApply(action), selection: selection)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -537,9 +545,9 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return TileViewDialog<EntrySortFactor, EntryGroupFactor, TileLayout>(
|
return TileViewDialog<EntrySortFactor, EntryGroupFactor, TileLayout>(
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
sortOptions: Map.fromEntries(_sortOptions.map((v) => MapEntry(v, v.getName(context)))),
|
sortOptions: _sortOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(),
|
||||||
groupOptions: Map.fromEntries(_groupOptions.map((v) => MapEntry(v, v.getName(context)))),
|
groupOptions: _groupOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(),
|
||||||
layoutOptions: Map.fromEntries(_layoutOptions.map((v) => MapEntry(v, v.getName(context)))),
|
layoutOptions: _layoutOptions.map((v) => TileViewDialogOption(value: v, title: v.getName(context), icon: v.icon)).toList(),
|
||||||
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
||||||
canGroup: (s, g, l) => s == EntrySortFactor.date,
|
canGroup: (s, g, l) => s == EntrySortFactor.date,
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/section_keys.dart';
|
import 'package:aves/model/source/section_keys.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
|
@ -25,7 +26,9 @@ import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||||
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
||||||
import 'package:aves/widgets/common/grid/item_tracker.dart';
|
import 'package:aves/widgets/common/grid/item_tracker.dart';
|
||||||
import 'package:aves/widgets/common/grid/scaling.dart';
|
import 'package:aves/widgets/common/grid/scaling.dart';
|
||||||
import 'package:aves/widgets/common/grid/section_layout.dart';
|
import 'package:aves/widgets/common/grid/sections/fixed/scale_grid.dart';
|
||||||
|
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
||||||
|
import 'package:aves/widgets/common/grid/sections/section_layout.dart';
|
||||||
import 'package:aves/widgets/common/grid/selector.dart';
|
import 'package:aves/widgets/common/grid/selector.dart';
|
||||||
import 'package:aves/widgets/common/grid/sliver.dart';
|
import 'package:aves/widgets/common/grid/sliver.dart';
|
||||||
import 'package:aves/widgets/common/grid/theme.dart';
|
import 'package:aves/widgets/common/grid/theme.dart';
|
||||||
|
@ -34,6 +37,7 @@ import 'package:aves/widgets/common/identity/empty.dart';
|
||||||
import 'package:aves/widgets/common/identity/scroll_thumb.dart';
|
import 'package:aves/widgets/common/identity/scroll_thumb.dart';
|
||||||
import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart';
|
import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/decorated.dart';
|
import 'package:aves/widgets/common/thumbnail/decorated.dart';
|
||||||
|
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||||
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
||||||
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
@ -50,7 +54,8 @@ class CollectionGrid extends StatefulWidget {
|
||||||
static const int columnCountDefault = 4;
|
static const int columnCountDefault = 4;
|
||||||
static const double extentMin = 46;
|
static const double extentMin = 46;
|
||||||
static const double extentMax = 300;
|
static const double extentMax = 300;
|
||||||
static const double spacing = 2;
|
static const double fixedExtentLayoutSpacing = 2;
|
||||||
|
static const double mosaicLayoutSpacing = 4;
|
||||||
|
|
||||||
const CollectionGrid({
|
const CollectionGrid({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -64,6 +69,8 @@ class CollectionGrid extends StatefulWidget {
|
||||||
class _CollectionGridState extends State<CollectionGrid> {
|
class _CollectionGridState extends State<CollectionGrid> {
|
||||||
TileExtentController? _tileExtentController;
|
TileExtentController? _tileExtentController;
|
||||||
|
|
||||||
|
String get settingsRouteKey => widget.settingsRouteKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tileExtentController?.dispose();
|
_tileExtentController?.dispose();
|
||||||
|
@ -72,14 +79,17 @@ class _CollectionGridState extends State<CollectionGrid> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_tileExtentController ??= TileExtentController(
|
final spacing = context.select<Settings, double>((s) => s.getTileLayout(settingsRouteKey) == TileLayout.mosaic ? CollectionGrid.mosaicLayoutSpacing : CollectionGrid.fixedExtentLayoutSpacing);
|
||||||
settingsRouteKey: widget.settingsRouteKey,
|
if (_tileExtentController?.spacing != spacing) {
|
||||||
columnCountDefault: CollectionGrid.columnCountDefault,
|
_tileExtentController = TileExtentController(
|
||||||
extentMin: CollectionGrid.extentMin,
|
settingsRouteKey: settingsRouteKey,
|
||||||
extentMax: CollectionGrid.extentMax,
|
columnCountDefault: CollectionGrid.columnCountDefault,
|
||||||
spacing: CollectionGrid.spacing,
|
extentMin: CollectionGrid.extentMin,
|
||||||
horizontalPadding: 2,
|
extentMax: CollectionGrid.extentMax,
|
||||||
);
|
spacing: spacing,
|
||||||
|
horizontalPadding: 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
return TileExtentControllerProvider(
|
return TileExtentControllerProvider(
|
||||||
controller: _tileExtentController!,
|
controller: _tileExtentController!,
|
||||||
child: _CollectionGridContent(),
|
child: _CollectionGridContent(),
|
||||||
|
@ -108,12 +118,13 @@ class _CollectionGridContent extends StatelessWidget {
|
||||||
final columnCount = c.item2;
|
final columnCount = c.item2;
|
||||||
final tileSpacing = c.item3;
|
final tileSpacing = c.item3;
|
||||||
final horizontalPadding = c.item4;
|
final horizontalPadding = c.item4;
|
||||||
|
final source = collection.source;
|
||||||
return GridTheme(
|
return GridTheme(
|
||||||
extent: thumbnailExtent,
|
extent: thumbnailExtent,
|
||||||
child: EntryListDetailsTheme(
|
child: EntryListDetailsTheme(
|
||||||
extent: thumbnailExtent,
|
extent: thumbnailExtent,
|
||||||
child: ValueListenableBuilder<SourceState>(
|
child: ValueListenableBuilder<SourceState>(
|
||||||
valueListenable: collection.source.stateNotifier,
|
valueListenable: source.stateNotifier,
|
||||||
builder: (context, sourceState, child) {
|
builder: (context, sourceState, child) {
|
||||||
late final Duration tileAnimationDelay;
|
late final Duration tileAnimationDelay;
|
||||||
if (sourceState == SourceState.ready) {
|
if (sourceState == SourceState.ready) {
|
||||||
|
@ -123,30 +134,37 @@ class _CollectionGridContent extends StatelessWidget {
|
||||||
} else {
|
} else {
|
||||||
tileAnimationDelay = Duration.zero;
|
tileAnimationDelay = Duration.zero;
|
||||||
}
|
}
|
||||||
return SectionedEntryListLayoutProvider(
|
|
||||||
collection: collection,
|
return StreamBuilder(
|
||||||
selectable: selectable,
|
stream: source.eventBus.on<AspectRatioChangedEvent>(),
|
||||||
scrollableWidth: scrollableWidth,
|
builder: (context, snapshot) => SectionedEntryListLayoutProvider(
|
||||||
tileLayout: tileLayout,
|
collection: collection,
|
||||||
columnCount: columnCount,
|
selectable: selectable,
|
||||||
spacing: tileSpacing,
|
scrollableWidth: scrollableWidth,
|
||||||
horizontalPadding: horizontalPadding,
|
tileLayout: tileLayout,
|
||||||
tileExtent: thumbnailExtent,
|
columnCount: columnCount,
|
||||||
tileBuilder: (entry) => AnimatedBuilder(
|
spacing: tileSpacing,
|
||||||
animation: favourites,
|
horizontalPadding: horizontalPadding,
|
||||||
builder: (context, child) {
|
tileExtent: thumbnailExtent,
|
||||||
return InteractiveTile(
|
tileBuilder: (entry, tileSize) {
|
||||||
key: ValueKey(entry.id),
|
final extent = tileSize.shortestSide;
|
||||||
collection: collection,
|
return AnimatedBuilder(
|
||||||
entry: entry,
|
animation: favourites,
|
||||||
thumbnailExtent: thumbnailExtent,
|
builder: (context, child) {
|
||||||
tileLayout: tileLayout,
|
return InteractiveTile(
|
||||||
isScrollingNotifier: _isScrollingNotifier,
|
key: ValueKey(entry.id),
|
||||||
|
collection: collection,
|
||||||
|
entry: entry,
|
||||||
|
thumbnailExtent: extent,
|
||||||
|
tileLayout: tileLayout,
|
||||||
|
isScrollingNotifier: _isScrollingNotifier,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
tileAnimationDelay: tileAnimationDelay,
|
||||||
|
child: child!,
|
||||||
),
|
),
|
||||||
tileAnimationDelay: tileAnimationDelay,
|
|
||||||
child: child!,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -260,12 +278,13 @@ class _CollectionScaler extends StatelessWidget {
|
||||||
final metrics = context.select<TileExtentController, Tuple2<double, double>>((v) => Tuple2(v.spacing, v.horizontalPadding));
|
final metrics = context.select<TileExtentController, Tuple2<double, double>>((v) => Tuple2(v.spacing, v.horizontalPadding));
|
||||||
final tileSpacing = metrics.item1;
|
final tileSpacing = metrics.item1;
|
||||||
final horizontalPadding = metrics.item2;
|
final horizontalPadding = metrics.item2;
|
||||||
|
final brightness = Theme.of(context).brightness;
|
||||||
return GridScaleGestureDetector<AvesEntry>(
|
return GridScaleGestureDetector<AvesEntry>(
|
||||||
scrollableKey: scrollableKey,
|
scrollableKey: scrollableKey,
|
||||||
tileLayout: tileLayout,
|
tileLayout: tileLayout,
|
||||||
heightForWidth: (width) => width,
|
heightForWidth: (width) => width,
|
||||||
gridBuilder: (center, tileSize, child) => CustomPaint(
|
gridBuilder: (center, tileSize, child) => CustomPaint(
|
||||||
painter: GridPainter(
|
painter: FixedExtentGridPainter(
|
||||||
tileLayout: tileLayout,
|
tileLayout: tileLayout,
|
||||||
tileCenter: center,
|
tileCenter: center,
|
||||||
tileSize: tileSize,
|
tileSize: tileSize,
|
||||||
|
@ -278,7 +297,7 @@ class _CollectionScaler extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
scaledBuilder: (entry, tileSize) => EntryListDetailsTheme(
|
scaledItemBuilder: (entry, tileSize) => EntryListDetailsTheme(
|
||||||
extent: tileSize.height,
|
extent: tileSize.height,
|
||||||
child: Tile(
|
child: Tile(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
|
@ -286,6 +305,15 @@ class _CollectionScaler extends StatelessWidget {
|
||||||
tileLayout: tileLayout,
|
tileLayout: tileLayout,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
mosaicItemBuilder: (index, targetExtent) => DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ThumbnailImage.computeLoadingBackgroundColor(index * 10, brightness).withOpacity(.9),
|
||||||
|
border: Border.all(
|
||||||
|
color: DecoratedThumbnail.borderColor,
|
||||||
|
width: DecoratedThumbnail.borderWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/utils/file_utils.dart';
|
import 'package:aves/utils/file_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
||||||
import 'package:aves/widgets/common/grid/section_layout.dart';
|
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
|
|
@ -370,6 +370,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
Set<String> obsoleteTags = todoItems.expand((entry) => entry.tags).toSet();
|
Set<String> obsoleteTags = todoItems.expand((entry) => entry.tags).toSet();
|
||||||
Set<String> obsoleteCountryCodes = todoItems.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.countryCode).whereNotNull().toSet();
|
Set<String> obsoleteCountryCodes = todoItems.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.countryCode).whereNotNull().toSet();
|
||||||
|
|
||||||
|
final Set<EntryDataType> dataTypes = {};
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
source.pauseMonitoring();
|
source.pauseMonitoring();
|
||||||
var cancelled = false;
|
var cancelled = false;
|
||||||
|
@ -379,8 +380,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return ImageOpEvent(success: true, skipped: true, uri: entry.uri);
|
return ImageOpEvent(success: true, skipped: true, uri: entry.uri);
|
||||||
} else {
|
} else {
|
||||||
final dataTypes = await op(entry);
|
final opDataTypes = await op(entry);
|
||||||
return ImageOpEvent(success: dataTypes.isNotEmpty, skipped: false, uri: entry.uri);
|
dataTypes.addAll(opDataTypes);
|
||||||
|
return ImageOpEvent(success: opDataTypes.isNotEmpty, skipped: false, uri: entry.uri);
|
||||||
}
|
}
|
||||||
}).asBroadcastStream(),
|
}).asBroadcastStream(),
|
||||||
itemCount: todoCount,
|
itemCount: todoCount,
|
||||||
|
@ -402,6 +404,10 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (dataTypes.contains(EntryDataType.aspectRatio)) {
|
||||||
|
source.onAspectRatioChanged();
|
||||||
|
}
|
||||||
|
|
||||||
if (showResult) {
|
if (showResult) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
final successCount = successOps.length;
|
final successCount = successOps.length;
|
||||||
|
|
|
@ -64,7 +64,7 @@ class _FilterBarState extends State<FilterBar> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: (context, animation) => _buildChip(filter),
|
: (context, animation) => const SizedBox(),
|
||||||
duration: animate ? Durations.filterBarRemovalAnimation : Duration.zero,
|
duration: animate ? Durations.filterBarRemovalAnimation : Duration.zero,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,8 +29,8 @@ class EntryListDetailsTheme extends StatelessWidget {
|
||||||
final textScaleFactor = mq.textScaleFactor;
|
final textScaleFactor = mq.textScaleFactor;
|
||||||
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final titleStyle = textTheme.bodyText2!;
|
final titleStyle = textTheme.bodyMedium!;
|
||||||
final captionStyle = textTheme.caption!;
|
final captionStyle = textTheme.bodySmall!;
|
||||||
|
|
||||||
final titleLineHeight = (RenderParagraph(
|
final titleLineHeight = (RenderParagraph(
|
||||||
TextSpan(text: 'Fake Title', style: titleStyle),
|
TextSpan(text: 'Fake Title', style: titleStyle),
|
||||||
|
|