Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2022-06-18 16:53:41 +09:00
commit 8357d84c66
151 changed files with 3914 additions and 724 deletions

View file

@ -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.0.1' flutter-version: '3.0.2'
channel: 'stable' channel: 'stable'
- name: Clone the repository. - name: Clone the repository.

View file

@ -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.0.1' flutter-version: '3.0.2'
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.0.1.sksl.json flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.0.2.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.0.1.sksl.json flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.0.2.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.0.1.sksl.json flutter build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders_3.0.2.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.0.1.sksl.json flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_3.0.2.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:

View file

@ -4,6 +4,26 @@ 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.6.9"></a>[v1.6.9] - 2022-06-18
### Added
- slideshow
- set wallpaper from any media
- optional dynamic accent color on Android 12+
- Search: date/dimension/size field equality (undocumented)
- support Android 13 (API 33)
- Turkish translation (thanks metezd)
### Changed
- do not force quit on storage permission denial
- upgraded Flutter to stable v3.0.2
### Fixed
- merge ambiguously cased directories
## <a id="v1.6.8"></a>[v1.6.8] - 2022-05-27 ## <a id="v1.6.8"></a>[v1.6.8] - 2022-05-27
### Fixed ### Fixed

View file

@ -12,6 +12,9 @@ Aves is a gallery and metadata explorer app. It is built for Android, with Flutt
[<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" [<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
alt='Get it on Google Play' alt='Get it on Google Play'
height="80">](https://play.google.com/store/apps/details?id=deckers.thibault.aves&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) height="80">](https://play.google.com/store/apps/details?id=deckers.thibault.aves&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/huawei-appgallery-badge-english-black.png"
alt='Get it on Huawei AppGallery'
height="80">](https://appgallery.huawei.com/app/C106014023)
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/amazon-appstore-badge-english-black.png" [<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/amazon-appstore-badge-english-black.png"
alt='Get it on Amazon Appstore' alt='Get it on Amazon Appstore'
height="80">](https://www.amazon.com/dp/B09XQHQQ72) height="80">](https://www.amazon.com/dp/B09XQHQQ72)
@ -90,7 +93,7 @@ At this stage this project does *not* accept PRs, except for translations.
### Translations ### Translations
If you want to translate this app in your language and share the result, [there is a guide](https://github.com/deckerst/aves/wiki/Contributing-to-Translations). English, Korean and French are already handled by me. Russian, German, Spanish, Portuguese, Indonesian, Japanese, Italian & Chinese are handled by generous volunteers. If you want to translate this app in your language and share the result, [there is a guide](https://github.com/deckerst/aves/wiki/Contributing-to-Translations). English, Korean and French are already handled by me. Russian, German, Spanish, Portuguese, Indonesian, Japanese, Italian, Chinese & Turkish are handled by generous volunteers.
### Donations ### Donations

View file

@ -41,7 +41,7 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 32 compileSdkVersion 33
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
@ -57,7 +57,7 @@ android {
// which implementation `DocumentBuilderImpl` is provided by the OS and is not customizable on Android, // which implementation `DocumentBuilderImpl` is provided by the OS and is not customizable on Android,
// but the implementation on API <19 is not robust enough and fails to build XMP documents // but the implementation on API <19 is not robust enough and fails to build XMP documents
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 32 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
manifestPlaceholders = [googleApiKey: keystoreProperties['googleApiKey']] manifestPlaceholders = [googleApiKey: keystoreProperties['googleApiKey']]
@ -154,7 +154,7 @@ 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.7.0' implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.exifinterface:exifinterface:1.3.3'
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'

View file

@ -6,16 +6,21 @@
Scoped storage on Android Q is inconvenient because users need to confirm edition on each individual file. Scoped storage on Android Q is inconvenient because users need to confirm edition on each individual file.
So we request `WRITE_EXTERNAL_STORAGE` until Q (29), and enable `requestLegacyExternalStorage` So we request `WRITE_EXTERNAL_STORAGE` until Q (29), and enable `requestLegacyExternalStorage`
--> -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- TODO TLAD [tiramisu] need notification permission? --> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission
<!-- TODO TLAD [tiramisu] READ_MEDIA_IMAGE, READ_MEDIA_VIDEO instead of READ_EXTERNAL_STORAGE? --> android:name="android.permission.READ_EXTERNAL_STORAGE"
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> android:maxSdkVersion="32" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" android:maxSdkVersion="29"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- to show foreground service progress via notification -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- to access media with original metadata with scoped storage (Android Q+) --> <!-- to access media with original metadata with scoped storage (Android Q+) -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
@ -128,6 +133,26 @@
android:name="android.app.searchable" android:name="android.app.searchable"
android:resource="@xml/searchable" /> android:resource="@xml/searchable" />
</activity> </activity>
<activity
android:name=".WallpaperActivity"
android:exported="true"
android:label="@string/wallpaper"
android:theme="@style/NormalTheme">
<intent-filter>
<action android:name="android.intent.action.ATTACH_DATA" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SET_WALLPAPER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service <service
android:name=".AnalysisService" android:name=".AnalysisService"
android:description="@string/analysis_service_description" android:description="@string/analysis_service_description"

View file

@ -159,7 +159,7 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
COMMAND_START -> { COMMAND_START -> {
runBlocking { runBlocking {
FlutterUtils.runOnUiThread { FlutterUtils.runOnUiThread {
val entryIds = data.get(KEY_ENTRY_IDS)?.takeIf { it is IntArray }?.let { (it as IntArray).toList() } val entryIds = data.getIntArray(KEY_ENTRY_IDS)?.toList()
backgroundChannel?.invokeMethod( backgroundChannel?.invokeMethod(
"start", hashMapOf( "start", hashMapOf(
"entryIds" to entryIds, "entryIds" to entryIds,

View file

@ -15,6 +15,7 @@ import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.streams.* import deckers.thibault.aves.channel.streams.*
import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getParcelableExtraCompat
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
@ -215,7 +216,7 @@ class MainActivity : FlutterActivity() {
} }
} }
Intent.ACTION_VIEW, Intent.ACTION_SEND, "com.android.camera.action.REVIEW" -> { Intent.ACTION_VIEW, Intent.ACTION_SEND, "com.android.camera.action.REVIEW" -> {
(intent.data ?: (intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri))?.let { uri -> (intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
// MIME type is optional // MIME type is optional
val type = intent.type ?: intent.resolveType(context) val type = intent.type ?: intent.resolveType(context)
return hashMapOf( return hashMapOf(
@ -332,6 +333,7 @@ class MainActivity : FlutterActivity() {
const val INTENT_ACTION_PICK = "pick" const val INTENT_ACTION_PICK = "pick"
const val INTENT_ACTION_SEARCH = "search" const val INTENT_ACTION_SEARCH = "search"
const val INTENT_ACTION_SET_WALLPAPER = "set_wallpaper"
const val INTENT_ACTION_VIEW = "view" const val INTENT_ACTION_VIEW = "view"
const val SHORTCUT_KEY_PAGE = "page" const val SHORTCUT_KEY_PAGE = "page"

View file

@ -0,0 +1,107 @@
package deckers.thibault.aves
import android.content.Intent
import android.net.Uri
import android.os.*
import android.util.Log
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getParcelableExtraCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.MethodChannel
class WallpaperActivity : FlutterActivity() {
private lateinit var intentDataMap: MutableMap<String, Any?>
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(LOG_TAG, "onCreate intent=$intent")
intent.extras?.takeUnless { it.isEmpty }?.let {
Log.i(LOG_TAG, "onCreate intent extras=$it")
}
super.onCreate(savedInstanceState)
val messenger = flutterEngine!!.dartExecutor.binaryMessenger
// dart -> platform -> dart
// - need Context
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
// - need Activity
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
MethodChannel(messenger, MediaFileHandler.CHANNEL).setMethodCallHandler(MediaFileHandler(this))
MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(WindowHandler(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)
MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"getIntentData" -> {
result.success(intentDataMap)
intentDataMap.clear()
}
}
}
}
override fun onStart() {
Log.i(LOG_TAG, "onStart")
super.onStart()
// as of Flutter v3.0.1, the window `viewInsets` and `viewPadding`
// are incorrect on startup in some environments (e.g. API 29 emulator),
// so we manually request to apply the insets to update the window metrics
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
Handler(Looper.getMainLooper()).postDelayed({
window.decorView.requestApplyInsets()
}, 100)
}
}
override fun onStop() {
Log.i(LOG_TAG, "onStop")
super.onStop()
}
override fun onDestroy() {
Log.i(LOG_TAG, "onDestroy")
super.onDestroy()
}
private fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
when (intent?.action) {
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
// MIME type is optional
val type = intent.type ?: intent.resolveType(context)
return hashMapOf(
MainActivity.INTENT_DATA_KEY_ACTION to MainActivity.INTENT_ACTION_SET_WALLPAPER,
MainActivity.INTENT_DATA_KEY_MIME_TYPE to type,
MainActivity.INTENT_DATA_KEY_URI to uri.toString(),
)
}
}
Intent.ACTION_RUN -> {
// flutter run
}
else -> {
Log.w(LOG_TAG, "unhandled intent action=${intent?.action}")
}
}
return HashMap()
}
companion object {
private val LOG_TAG = LogUtils.createTag<WallpaperActivity>()
const val VIEWER_CHANNEL = "deckers.thibault/aves/viewer"
}
}

View file

@ -30,6 +30,8 @@ import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getApplicationInfoCompat
import deckers.thibault.aves.utils.queryIntentActivitiesCompat
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -77,7 +79,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
} }
val pm = context.packageManager val pm = context.packageManager
for (resolveInfo in pm.queryIntentActivities(intent, 0)) { for (resolveInfo in pm.queryIntentActivitiesCompat(intent, 0)) {
val appInfo = resolveInfo.activityInfo.applicationInfo val appInfo = resolveInfo.activityInfo.applicationInfo
val packageName = appInfo.packageName val packageName = appInfo.packageName
if (!packages.containsKey(packageName)) { if (!packages.containsKey(packageName)) {
@ -149,7 +151,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
val size = (sizeDip * density).roundToInt() val size = (sizeDip * density).roundToInt()
var data: ByteArray? = null var data: ByteArray? = null
try { try {
val iconResourceId = context.packageManager.getApplicationInfo(packageName, 0).icon val iconResourceId = context.packageManager.getApplicationInfoCompat(packageName, 0).icon
if (iconResourceId != Resources.ID_NULL) { if (iconResourceId != Resources.ID_NULL) {
val uri = Uri.Builder() val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
@ -444,4 +446,4 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
} }
} }
} }
} }

View file

@ -32,6 +32,8 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context), "canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
"canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT), "canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT),
"canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP), "canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
"canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N),
"isDynamicColorAvailable" to (sdkInt >= Build.VERSION_CODES.S),
"showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O), "showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O),
"supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q), "supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q),
) )

View file

@ -1,12 +1,17 @@
package deckers.thibault.aves.channel.calls package deckers.thibault.aves.channel.calls
import android.content.Context import android.content.Context
import android.location.Address
import android.location.Geocoder import android.location.Geocoder
import android.os.Build
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
@ -48,36 +53,48 @@ class GeocodingHandler(private val context: Context) : MethodCallHandler {
Geocoder(context) Geocoder(context)
} }
val addresses = try { fun processAddresses(addresses: List<Address>) {
geocoder!!.getFromLocation(latitude, longitude, maxResults) ?: ArrayList() if (addresses.isEmpty()) {
} catch (e: IOException) { result.error("getAddress-empty", "failed to find any address for latitude=$latitude, longitude=$longitude", null)
// `grpc failed`, etc. } else {
result.error("getAddress-network", "failed to get address because of network issues", e.message) val addressMapList: ArrayList<Map<String, String?>> = ArrayList(addresses.map { address ->
return hashMapOf(
} catch (e: Exception) { "addressLine" to (0..address.maxAddressLineIndex).joinToString(", ") { i -> address.getAddressLine(i) },
result.error("getAddress-exception", "failed to get address", e.message) "adminArea" to address.adminArea,
return "countryCode" to address.countryCode,
"countryName" to address.countryName,
"featureName" to address.featureName,
"locality" to address.locality,
"postalCode" to address.postalCode,
"subAdminArea" to address.subAdminArea,
"subLocality" to address.subLocality,
"subThoroughfare" to address.subThoroughfare,
"thoroughfare" to address.thoroughfare,
)
})
result.success(addressMapList)
}
} }
if (addresses.isEmpty()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
result.error("getAddress-empty", "failed to find any address for latitude=$latitude, longitude=$longitude", null) geocoder!!.getFromLocation(latitude, longitude, maxResults, object : Geocoder.GeocodeListener {
} else { override fun onGeocode(addresses: List<Address?>) = processAddresses(addresses.filterNotNull())
val addressMapList: ArrayList<Map<String, String?>> = ArrayList(addresses.map { address ->
hashMapOf( override fun onError(errorMessage: String?) {
"addressLine" to (0..address.maxAddressLineIndex).joinToString(", ") { i -> address.getAddressLine(i) }, result.error("getAddress-asyncerror", "failed to get address", errorMessage)
"adminArea" to address.adminArea, }
"countryCode" to address.countryCode,
"countryName" to address.countryName,
"featureName" to address.featureName,
"locality" to address.locality,
"postalCode" to address.postalCode,
"subAdminArea" to address.subAdminArea,
"subLocality" to address.subLocality,
"subThoroughfare" to address.subThoroughfare,
"thoroughfare" to address.thoroughfare,
)
}) })
result.success(addressMapList) } else {
try {
@Suppress("deprecation")
val addresses = geocoder!!.getFromLocation(latitude, longitude, maxResults) ?: ArrayList()
processAddresses(addresses)
} catch (e: IOException) {
// `grpc failed`, etc.
result.error("getAddress-network", "failed to get address because of network issues", e.message)
} catch (e: Exception) {
result.error("getAddress-exception", "failed to get address", e.message)
}
} }
} }

View file

@ -0,0 +1,58 @@
package deckers.thibault.aves.channel.calls
import android.app.Activity
import android.app.WallpaperManager
import android.app.WallpaperManager.FLAG_LOCK
import android.app.WallpaperManager.FLAG_SYSTEM
import android.os.Build
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class WallpaperHandler(private val activity: Activity) : MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"setWallpaper" -> ioScope.launch { safe(call, result, ::setWallpaper) }
else -> result.notImplemented()
}
}
private fun setWallpaper(call: MethodCall, result: MethodChannel.Result) {
val bytes = call.argument<ByteArray>("bytes")
val home = call.argument<Boolean>("home")
val lock = call.argument<Boolean>("lock")
if (bytes == null || home == null || lock == null) {
result.error("setWallpaper-args", "failed because of missing arguments", null)
return
}
val manager = WallpaperManager.getInstance(activity)
val supported = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || manager.isWallpaperSupported
val allowed = Build.VERSION.SDK_INT < Build.VERSION_CODES.N || manager.isSetWallpaperAllowed
if (!supported || !allowed) {
result.error("setWallpaper-unsupported", "failed because setting wallpaper is not allowed", null)
return
}
bytes.inputStream().use { input ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val flags = (if (home) FLAG_SYSTEM else 0) or (if (lock) FLAG_LOCK else 0)
manager.setStream(input, null, true, flags)
} else {
manager.setStream(input)
}
}
result.success(true)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/wallpaper"
}
}

View file

@ -220,7 +220,7 @@ object ExifInterfaceHelper {
// initialize metadata-extractor directories that we will fill // initialize metadata-extractor directories that we will fill
// by tags converted from the ExifInterface attributes // by tags converted from the ExifInterface attributes
// so that we can rely on metadata-extractor descriptions // so that we can rely on metadata-extractor descriptions
val dirs = DirType.values().associate { Pair(it, it.createDirectory()) } val dirs = DirType.values().associateWith { it.createDirectory() }
// exclude Exif directory when it only includes image size // exclude Exif directory when it only includes image size
val isUselessExif = fun(it: Map<String, String>): Boolean { val isUselessExif = fun(it: Map<String, String>): Boolean {

View file

@ -0,0 +1,37 @@
package deckers.thibault.aves.utils
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.Build
import android.os.Parcelable
inline fun <reified T> Intent.getParcelableExtraCompat(name: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableExtra(name, T::class.java)
} else {
@Suppress("deprecation")
getParcelableExtra<Parcelable>(name) as? T
}
}
fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong()))
} else {
@Suppress("deprecation")
getApplicationInfo(packageName, flags)
}
}
fun PackageManager.queryIntentActivitiesCompat(intent: Intent, flags: Int): List<ResolveInfo> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(flags.toLong()))
} else {
@Suppress("deprecation")
queryIntentActivities(intent, flags)
}
}

View file

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<group
android:scaleX=".44"
android:scaleY=".44"
android:translateX="28"
android:translateY="30">
<path
android:pathData="M3.925,16.034 L60.825,72.933a2.421,2.421 0.001,0 0,3.423 0l10.604,-10.603a6.789,6.789 90.001,0 0,0 -9.601L34.066,11.942A8.264,8.264 22.5,0 0,28.222 9.522H6.623A3.815,3.815 112.5,0 0,3.925 16.034Z"
android:strokeWidth="5"
android:strokeColor="#000000"
android:strokeLineJoin="round" />
<path
android:pathData="m36.36,65.907v28.743a2.557,2.557 22.5,0 0,4.364 1.808L53.817,83.364a6.172,6.172 90,0 0,0 -8.729L42.532,63.35a3.616,3.616 157.5,0 0,-6.172 2.557z"
android:strokeWidth="5"
android:strokeColor="#000000"
android:strokeLineJoin="round" />
<path
android:pathData="M79.653,40.078V11.335A2.557,2.557 22.5,0 0,75.289 9.527L62.195,22.62a6.172,6.172 90,0 0,0 8.729l11.285,11.285a3.616,3.616 157.5,0 0,6.172 -2.557z"
android:strokeWidth="5"
android:strokeColor="#000000"
android:strokeLineJoin="round" />
<path
android:pathData="M96.613,16.867 L89.085,9.339a1.917,1.917 157.5,0 0,-3.273 1.356v6.172a4.629,4.629 45,0 0,4.629 4.629h4.255a2.712,2.712 112.5,0 0,1.917 -4.629z"
android:strokeWidth="5"
android:strokeColor="#000000"
android:strokeLineJoin="round" />
</group>
</vector>

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_mono" />
</adaptive-icon> </adaptive-icon>

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_mono" />
</adaptive-icon> </adaptive-icon>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Hintergrundbild</string>
<string name="search_shortcut_short_label">Suche</string> <string name="search_shortcut_short_label">Suche</string>
<string name="videos_shortcut_short_label">Videos</string> <string name="videos_shortcut_short_label">Videos</string>
<string name="analysis_channel_name">Analyse von Medien</string> <string name="analysis_channel_name">Analyse von Medien</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Fondo de pantalla</string>
<string name="search_shortcut_short_label">Búsqueda</string> <string name="search_shortcut_short_label">Búsqueda</string>
<string name="videos_shortcut_short_label">Videos</string> <string name="videos_shortcut_short_label">Videos</string>
<string name="analysis_channel_name">Explorar medios</string> <string name="analysis_channel_name">Explorar medios</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Fond décran</string>
<string name="search_shortcut_short_label">Recherche</string> <string name="search_shortcut_short_label">Recherche</string>
<string name="videos_shortcut_short_label">Vidéos</string> <string name="videos_shortcut_short_label">Vidéos</string>
<string name="analysis_channel_name">Analyse des images</string> <string name="analysis_channel_name">Analyse des images</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Wallpaper</string>
<string name="search_shortcut_short_label">Cari</string> <string name="search_shortcut_short_label">Cari</string>
<string name="videos_shortcut_short_label">Video</string> <string name="videos_shortcut_short_label">Video</string>
<string name="analysis_channel_name">Pindai media</string> <string name="analysis_channel_name">Pindai media</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Sfondo</string>
<string name="search_shortcut_short_label">Ricerca</string> <string name="search_shortcut_short_label">Ricerca</string>
<string name="videos_shortcut_short_label">Video</string> <string name="videos_shortcut_short_label">Video</string>
<string name="analysis_channel_name">Scansione media</string> <string name="analysis_channel_name">Scansione media</string>

View file

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="search_shortcut_short_label">検索</string> <string name="wallpaper">壁紙</string>
<string name="videos_shortcut_short_label">動画</string> <string name="search_shortcut_short_label">検索</string>
<string name="analysis_channel_name">メディアスキャン</string> <string name="videos_shortcut_short_label">動画</string>
<string name="analysis_service_description">画像と動画をスキャン</string> <string name="analysis_channel_name">メディアスキャン</string>
<string name="analysis_notification_default_title">メディアをスキャン中</string> <string name="analysis_service_description">画像と動画をスキャン</string>
<string name="analysis_notification_action_stop">停止</string> <string name="analysis_notification_default_title">メディアをスキャン中</string>
<string name="analysis_notification_action_stop">停止</string>
</resources> </resources>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">아베스</string> <string name="app_name">아베스</string>
<string name="wallpaper">배경화면</string>
<string name="search_shortcut_short_label">검색</string> <string name="search_shortcut_short_label">검색</string>
<string name="videos_shortcut_short_label">동영상</string> <string name="videos_shortcut_short_label">동영상</string>
<string name="analysis_channel_name">미디어 분석</string> <string name="analysis_channel_name">미디어 분석</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Papel de parede</string>
<string name="search_shortcut_short_label">Procurar</string> <string name="search_shortcut_short_label">Procurar</string>
<string name="videos_shortcut_short_label">Vídeos</string> <string name="videos_shortcut_short_label">Vídeos</string>
<string name="analysis_channel_name">Digitalização de mídia</string> <string name="analysis_channel_name">Digitalização de mídia</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Обои</string>
<string name="search_shortcut_short_label">Поиск</string> <string name="search_shortcut_short_label">Поиск</string>
<string name="videos_shortcut_short_label">Видео</string> <string name="videos_shortcut_short_label">Видео</string>
<string name="analysis_channel_name">Сканировать медия</string> <string name="analysis_channel_name">Сканировать медия</string>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Aves</string>
<string name="wallpaper">Duvar kağıdı</string>
<string name="search_shortcut_short_label">Arama</string>
<string name="videos_shortcut_short_label">Videolar</string>
<string name="analysis_channel_name">Medya tarama</string>
<string name="analysis_service_description">Görüntüleri ve videoları tarayın</string>
<string name="analysis_notification_default_title">Medya taranıyor</string>
<string name="analysis_notification_action_stop">Durdur</string>
</resources>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">壁纸</string>
<string name="search_shortcut_short_label">搜索</string> <string name="search_shortcut_short_label">搜索</string>
<string name="videos_shortcut_short_label">视频</string> <string name="videos_shortcut_short_label">视频</string>
<string name="analysis_channel_name">媒体扫描</string> <string name="analysis_channel_name">媒体扫描</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="wallpaper">Wallpaper</string>
<string name="search_shortcut_short_label">Search</string> <string name="search_shortcut_short_label">Search</string>
<string name="videos_shortcut_short_label">Videos</string> <string name="videos_shortcut_short_label">Videos</string>
<string name="analysis_channel_name">Media scan</string> <string name="analysis_channel_name">Media scan</string>

View file

@ -1,17 +1,17 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.6.21' ext.kotlin_version = '1.7.0'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven { url 'https://developer.huawei.com/repo/' } maven { url 'https://developer.huawei.com/repo/' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.0' classpath 'com.android.tools.build:gradle:7.2.1'
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.10' classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.0'
// 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.5.2.300'
} }

View file

@ -2,4 +2,4 @@
<b>Navigation und Suche</b> ist ein wichtiger Bestandteil von <i>Aves</i>. Das Ziel besteht darin, dass Benutzer problemlos von Alben zu Fotos zu Tags zu Karten usw. wechseln können. <b>Navigation und Suche</b> ist ein wichtiger Bestandteil von <i>Aves</i>. Das Ziel besteht darin, dass Benutzer problemlos von Alben zu Fotos zu Tags zu Karten usw. wechseln können.
<i>Aves</i> lässt sich mit Android (von <b>API 19 bis 32</b>, d. h. von KitKat bis Android 12L) mit Funktionen wie <b>App-Verknüpfungen</b> und <b>globaler Suche</b> integrieren. Es funktioniert auch als <b>Medienbetrachter und -auswahl</b>. <i>Aves</i> lässt sich mit Android (von <b>API 19 bis 33</b>, d. h. von KitKat bis Android 13) mit Funktionen wie <b>App-Verknüpfungen</b> und <b>globaler Suche</b> integrieren. Es funktioniert auch als <b>Medienbetrachter und -auswahl</b>.

View file

@ -0,0 +1,5 @@
In v1.6.9:
- start slideshows
- change your wallpaper
- enjoy the app in Turkish
Full changelog available on GitHub

View file

@ -2,4 +2,4 @@
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc. <b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc.
<i>Aves</i> integrates with Android (from <b>API 19 to 32</b>, i.e. from KitKat to Android 12L) with features such as <b>app shortcuts</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>. <i>Aves</i> integrates with Android (from <b>API 19 to 33</b>, i.e. from KitKat to Android 13) with features such as <b>app shortcuts</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>.

View file

@ -2,4 +2,4 @@
La <b>navegación y búsqueda</b> son partes importantes de <i>Aves</i>. Su propósito es que los usuarios puedan fácimente ir de álbumes a fotos, etiquetas, mapas, etc. La <b>navegación y búsqueda</b> son partes importantes de <i>Aves</i>. Su propósito es que los usuarios puedan fácimente ir de álbumes a fotos, etiquetas, mapas, etc.
<i>Aves</i> se integra con Android (desde <b>API 19 a 32</b>, por ej. desde KitKat hasta Android 12L) con características como <b>vínculos de aplicación</b> y manejo de <b>búsqueda global</b>. También funciona como un <b>visor y seleccionador multimedia</b>. <i>Aves</i> se integra con Android (desde <b>API 19 a 33</b>, por ej. desde KitKat hasta Android 13) con características como <b>vínculos de aplicación</b> y manejo de <b>búsqueda global</b>. También funciona como un <b>visor y seleccionador multimedia</b>.

View file

@ -2,4 +2,4 @@
<b>Navigasi dan pencarian</b> merupakan bagian penting dari <i>Aves</i>. Tujuannya adalah agar pengguna dengan mudah mengalir dari album ke foto ke tag ke peta, dll. <b>Navigasi dan pencarian</b> merupakan bagian penting dari <i>Aves</i>. Tujuannya adalah agar pengguna dengan mudah mengalir dari album ke foto ke tag ke peta, dll.
<i>Aves</i> terintegrasi dengan Android (dari <b>API 19 ke 32</b>, yaitu dari KitKat ke Android 12L) dengan fitur-fitur seperti <b>pintasan aplikasi</b> dan <b>pencarian global</b> penanganan. Ini juga berfungsi sebagai <b>penampil dan pemilih media</b>. <i>Aves</i> terintegrasi dengan Android (dari <b>API 19 ke 33</b>, yaitu dari KitKat ke Android 13) dengan fitur-fitur seperti <b>pintasan aplikasi</b> dan <b>pencarian global</b> penanganan. Ini juga berfungsi sebagai <b>penampil dan pemilih media</b>.

View file

@ -2,4 +2,4 @@
<b>Navigazione e ricerca</b> sono una parte importante di <i>Aves</i>. L'obiettivo è che gli utenti passino facilmente dagli album alle foto, ai tag, alle mappe, ecc. <b>Navigazione e ricerca</b> sono una parte importante di <i>Aves</i>. L'obiettivo è che gli utenti passino facilmente dagli album alle foto, ai tag, alle mappe, ecc.
<i>Aves</i> si integra con Android (da <b>API 19 a 32</b>, cioè da KitKat ad Android 12L) con caratteristiche come <b>collegamenti alle app</b> e la gestione della <b>ricerca globale</b>. Funziona anche come <b>visualizzazione e raccolta di media</b>. <i>Aves</i> si integra con Android (da <b>API 19 a 33</b>, cioè da KitKat ad Android 13) con caratteristiche come <b>collegamenti alle app</b> e la gestione della <b>ricerca globale</b>. Funziona anche come <b>visualizzazione e raccolta di media</b>.

View file

@ -4,4 +4,4 @@
<b>ナビゲーションと検索</b>は、Avesの重要な部分です。アルバムから写真、タグ、地図などへ簡単に移動できます。 <b>ナビゲーションと検索</b>は、Avesの重要な部分です。アルバムから写真、タグ、地図などへ簡単に移動できます。
<i>Aves</i>は、<b>アプリショートカット</b>や<b>グローバル検索</b>などの機能を、Android<b>API 19から32まで</b>、つまりAndroid 4.4から12 Lまでと統合しています。また、<b>メディアビューワー</b>や<b>メディアピッカー</b>としても機能します。 <i>Aves</i>は、<b>アプリショートカット</b>や<b>グローバル検索</b>などの機能を、Android<b>API 19から33まで</b>、つまりAndroid 4.4から13 Lまでと統合しています。また、<b>メディアビューワー</b>や<b>メディアピッカー</b>としても機能します。

View file

@ -2,4 +2,4 @@
<b>Navegação e pesquisa</b> é uma parte importante do <i>Aves</i>. O objetivo é que os usuários fluam facilmente de álbuns para fotos, etiquetas, mapas, etc. <b>Navegação e pesquisa</b> é uma parte importante do <i>Aves</i>. O objetivo é que os usuários fluam facilmente de álbuns para fotos, etiquetas, mapas, etc.
<i>Aves</i> integra com Android (de <b>API 19 para 32</b>, i.e. de KitKat para Android 12L) com recursos como <b>atalhos de apps</b> e <b>pesquisa global</b> manipulação. Também funciona como um <b>visualizador e selecionador de mídia</b>. <i>Aves</i> integra com Android (de <b>API 19 para 33</b>, i.e. de KitKat para Android 13) com recursos como <b>atalhos de apps</b> e <b>pesquisa global</b> manipulação. Também funciona como um <b>visualizador e selecionador de mídia</b>.

View file

@ -0,0 +1,5 @@
<i>Aves</i> tipik JPEG ve MP4'lerin yanı sıra <b>çok sayfalı TIFF'ler, SVG'ler, eski AVI'ler ve daha fazlası</b> gibi daha egzotik şeyler de dahil olmak üzere her türlü görüntü ve videoyu işleyebilir! <b>Hareketli fotoğrafları</b>, <b>panoramaları</b> (fotoğraf küreleri olarak da bilinir), <b>360° videoları</b> ve <b>GeoTIFF</b> dosyalarını tanımlamak için medya koleksiyonunuzu tarar.
<b>Gezinme ve arama</b> <i>Aves'in</i> önemli bir parçasıdır. Amaç, kullanıcıların albümlerden fotoğraflara, etiketlerden haritalara vb. kolayca geçmesini sağlamaktır.
<i>Aves</i>, <b>uygulama kısayolları</b> ve <b>global arama<b> işleme gibi özelliklerle Android (<b>API 19'dan 33'ye</b>, yani KitKat'tan Android 13'ye kadar) ile entegre olur. Ayrıca bir <b>medya görüntüleyici ve alıcı</b> olarak da çalışır.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

View file

@ -0,0 +1 @@
Galeri ve meta veri gezgini

View file

@ -2,4 +2,4 @@
<b>导航与搜索</b>是 <i>Aves</i> 的核心功能之一,旨在帮助用户在相册、照片、标签、地图等之间轻松切换。 <b>导航与搜索</b>是 <i>Aves</i> 的核心功能之一,旨在帮助用户在相册、照片、标签、地图等之间轻松切换。
<i> Aves</i> 与 Android<b>API 19-32</b>,即从 KitKat 到 Android 12L)集成,具有<b>快捷方式</b>和<b>全局搜索</b>等功能。它还可用作<b>媒体查看器和选择器<b>。 <i> Aves</i> 与 Android<b>API 19-33</b>,即从 KitKat 到 Android 13)集成,具有<b>快捷方式</b>和<b>全局搜索</b>等功能。它还可用作<b>媒体查看器和选择器<b>。

View file

@ -1,4 +1,13 @@
enum AppMode { main, pickSingleMediaExternal, pickMultipleMediaExternal, pickMediaInternal, pickFilterInternal, view } enum AppMode {
main,
pickSingleMediaExternal,
pickMultipleMediaExternal,
pickMediaInternal,
pickFilterInternal,
setWallpaper,
slideshow,
view,
}
extension ExtraAppMode on AppMode { extension ExtraAppMode on AppMode {
bool get canSearch => this == AppMode.main || this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal; bool get canSearch => this == AppMode.main || this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal;

View file

@ -50,6 +50,7 @@
"entryActionDelete": "Löschen", "entryActionDelete": "Löschen",
"entryActionConvert": "Konvertieren", "entryActionConvert": "Konvertieren",
"entryActionExport": "Exportieren", "entryActionExport": "Exportieren",
"entryActionInfo": "Info",
"entryActionRename": "Umbenennen", "entryActionRename": "Umbenennen",
"entryActionRestore": "Wiederherstellen", "entryActionRestore": "Wiederherstellen",
"entryActionRotateCCW": "Drehen gegen den Uhrzeigersinn", "entryActionRotateCCW": "Drehen gegen den Uhrzeigersinn",
@ -80,6 +81,9 @@
"videoActionSetSpeed": "Wiedergabegeschwindigkeit", "videoActionSetSpeed": "Wiedergabegeschwindigkeit",
"videoActionSettings": "Einstellungen", "videoActionSettings": "Einstellungen",
"slideshowActionResume": "Wiedergabe",
"slideshowActionShowInCollection": "In Sammlung anzeigen",
"entryInfoActionEditDate": "Datum & Uhrzeit bearbeiten", "entryInfoActionEditDate": "Datum & Uhrzeit bearbeiten",
"entryInfoActionEditLocation": "Standort bearbeiten", "entryInfoActionEditLocation": "Standort bearbeiten",
"entryInfoActionEditRating": "Bewertung bearbeiten", "entryInfoActionEditRating": "Bewertung bearbeiten",
@ -144,10 +148,23 @@
"displayRefreshRatePreferHighest": "Höchste Rate", "displayRefreshRatePreferHighest": "Höchste Rate",
"displayRefreshRatePreferLowest": "Niedrigste Rate", "displayRefreshRatePreferLowest": "Niedrigste Rate",
"slideshowVideoPlaybackSkip": "Überspringen",
"slideshowVideoPlaybackMuted": "Stumm abspielen",
"slideshowVideoPlaybackWithSound": "Mit Ton abspielen",
"themeBrightnessLight": "Hell", "themeBrightnessLight": "Hell",
"themeBrightnessDark": "Dunkel", "themeBrightnessDark": "Dunkel",
"themeBrightnessBlack": "Schwarz", "themeBrightnessBlack": "Schwarz",
"viewerTransitionSlide": "Dia",
"viewerTransitionParallax": "Parallaxe",
"viewerTransitionFade": "Ausblenden",
"viewerTransitionZoomIn": "Heranzoomen",
"wallpaperTargetHome": "Startbildschirm",
"wallpaperTargetLock": "Sperrbildschirm",
"wallpaperTargetHomeLock": "Start- und Sperrbildschirm",
"albumTierNew": "Neu", "albumTierNew": "Neu",
"albumTierPinned": "Angeheftet", "albumTierPinned": "Angeheftet",
"albumTierSpecial": "Häufig verwendet", "albumTierSpecial": "Häufig verwendet",
@ -262,6 +279,7 @@
"menuActionSelectAll": "Alle auswählen", "menuActionSelectAll": "Alle auswählen",
"menuActionSelectNone": "Keine auswählen", "menuActionSelectNone": "Keine auswählen",
"menuActionMap": "Karte", "menuActionMap": "Karte",
"menuActionSlideshow": "Diashow",
"menuActionStats": "Statistiken", "menuActionStats": "Statistiken",
"viewDialogTabSort": "Sortieren", "viewDialogTabSort": "Sortieren",
@ -349,6 +367,7 @@
"collectionEmptyFavourites": "Keine Favoriten", "collectionEmptyFavourites": "Keine Favoriten",
"collectionEmptyVideos": "Keine Videos", "collectionEmptyVideos": "Keine Videos",
"collectionEmptyImages": "Keine Bilder", "collectionEmptyImages": "Keine Bilder",
"collectionEmptyGrantAccessButtonLabel": "Zugriff gewähren",
"collectionSelectSectionTooltip": "Bereich auswählen", "collectionSelectSectionTooltip": "Bereich auswählen",
"collectionDeselectSectionTooltip": "Bereich abwählen", "collectionDeselectSectionTooltip": "Bereich abwählen",
@ -479,6 +498,17 @@
"settingsViewerShowOverlayThumbnails": "Vorschaubilder anzeigen", "settingsViewerShowOverlayThumbnails": "Vorschaubilder anzeigen",
"settingsViewerEnableOverlayBlurEffect": "Unschärfe-Effekt", "settingsViewerEnableOverlayBlurEffect": "Unschärfe-Effekt",
"settingsViewerSlideshowTile": "Diashow",
"settingsViewerSlideshowTitle": "Diashow",
"settingsSlideshowRepeat": "Wiederholung",
"settingsSlideshowShuffle": "Mischen",
"settingsSlideshowTransitionTile": "Übergang",
"settingsSlideshowTransitionTitle": "Übergang",
"settingsSlideshowIntervalTile": "Intervall",
"settingsSlideshowIntervalTitle": "Intervall",
"settingsSlideshowVideoPlaybackTile": "Videowiedergabe",
"settingsSlideshowVideoPlaybackTitle": "Videowiedergabe",
"settingsVideoPageTitle": "Video-Einstellungen", "settingsVideoPageTitle": "Video-Einstellungen",
"settingsSectionVideo": "Video", "settingsSectionVideo": "Video",
"settingsVideoShowVideos": "Videos anzeigen", "settingsVideoShowVideos": "Videos anzeigen",
@ -543,6 +573,7 @@
"settingsSectionDisplay": "Anzeige", "settingsSectionDisplay": "Anzeige",
"settingsThemeBrightness": "Thema", "settingsThemeBrightness": "Thema",
"settingsThemeColorHighlights": "Farbige Highlights", "settingsThemeColorHighlights": "Farbige Highlights",
"settingsThemeEnableDynamicColor": "Dynamische Farben",
"settingsDisplayRefreshRateModeTile": "Bildwiederholrate der Anzeige", "settingsDisplayRefreshRateModeTile": "Bildwiederholrate der Anzeige",
"settingsDisplayRefreshRateModeTitle": "Bildwiederholrate", "settingsDisplayRefreshRateModeTitle": "Bildwiederholrate",
@ -560,6 +591,7 @@
"statsTopTags": "Top-Tags", "statsTopTags": "Top-Tags",
"viewerOpenPanoramaButtonLabel": "ÖFFNE PANORAMA", "viewerOpenPanoramaButtonLabel": "ÖFFNE PANORAMA",
"viewerSetWallpaperButtonLabel": "HINTERGRUNDBILD EINSTELLEN",
"viewerErrorUnknown": "Ups!", "viewerErrorUnknown": "Ups!",
"viewerErrorDoesNotExist": "Die Datei existiert nicht mehr.", "viewerErrorDoesNotExist": "Die Datei existiert nicht mehr.",

View file

@ -78,6 +78,7 @@
"entryActionDelete": "Delete", "entryActionDelete": "Delete",
"entryActionConvert": "Convert", "entryActionConvert": "Convert",
"entryActionExport": "Export", "entryActionExport": "Export",
"entryActionInfo": "Info",
"entryActionRename": "Rename", "entryActionRename": "Rename",
"entryActionRestore": "Restore", "entryActionRestore": "Restore",
"entryActionRotateCCW": "Rotate counterclockwise", "entryActionRotateCCW": "Rotate counterclockwise",
@ -108,6 +109,9 @@
"videoActionSetSpeed": "Playback speed", "videoActionSetSpeed": "Playback speed",
"videoActionSettings": "Settings", "videoActionSettings": "Settings",
"slideshowActionResume": "Resume",
"slideshowActionShowInCollection": "Show in Collection",
"entryInfoActionEditDate": "Edit date & time", "entryInfoActionEditDate": "Edit date & time",
"entryInfoActionEditLocation": "Edit location", "entryInfoActionEditLocation": "Edit location",
"entryInfoActionEditRating": "Edit rating", "entryInfoActionEditRating": "Edit rating",
@ -184,10 +188,23 @@
"displayRefreshRatePreferHighest": "Highest rate", "displayRefreshRatePreferHighest": "Highest rate",
"displayRefreshRatePreferLowest": "Lowest rate", "displayRefreshRatePreferLowest": "Lowest rate",
"slideshowVideoPlaybackSkip": "Skip",
"slideshowVideoPlaybackMuted": "Play muted",
"slideshowVideoPlaybackWithSound": "Play with sound",
"themeBrightnessLight": "Light", "themeBrightnessLight": "Light",
"themeBrightnessDark": "Dark", "themeBrightnessDark": "Dark",
"themeBrightnessBlack": "Black", "themeBrightnessBlack": "Black",
"viewerTransitionSlide": "Slide",
"viewerTransitionParallax": "Parallax",
"viewerTransitionFade": "Fade",
"viewerTransitionZoomIn": "Zoom in",
"wallpaperTargetHome": "Home screen",
"wallpaperTargetLock": "Lock screen",
"wallpaperTargetHomeLock": "Home and lock screens",
"albumTierNew": "New", "albumTierNew": "New",
"albumTierPinned": "Pinned", "albumTierPinned": "Pinned",
"albumTierSpecial": "Common", "albumTierSpecial": "Common",
@ -392,6 +409,7 @@
"menuActionSelectAll": "Select all", "menuActionSelectAll": "Select all",
"menuActionSelectNone": "Select none", "menuActionSelectNone": "Select none",
"menuActionMap": "Map", "menuActionMap": "Map",
"menuActionSlideshow": "Slideshow",
"menuActionStats": "Stats", "menuActionStats": "Stats",
"viewDialogTabSort": "Sort", "viewDialogTabSort": "Sort",
@ -529,6 +547,7 @@
"collectionEmptyFavourites": "No favorites", "collectionEmptyFavourites": "No favorites",
"collectionEmptyVideos": "No videos", "collectionEmptyVideos": "No videos",
"collectionEmptyImages": "No images", "collectionEmptyImages": "No images",
"collectionEmptyGrantAccessButtonLabel": "Grant access",
"collectionSelectSectionTooltip": "Select section", "collectionSelectSectionTooltip": "Select section",
"collectionDeselectSectionTooltip": "Deselect section", "collectionDeselectSectionTooltip": "Deselect section",
@ -659,6 +678,17 @@
"settingsViewerShowOverlayThumbnails": "Show thumbnails", "settingsViewerShowOverlayThumbnails": "Show thumbnails",
"settingsViewerEnableOverlayBlurEffect": "Blur effect", "settingsViewerEnableOverlayBlurEffect": "Blur effect",
"settingsViewerSlideshowTile": "Slideshow",
"settingsViewerSlideshowTitle": "Slideshow",
"settingsSlideshowRepeat": "Repeat",
"settingsSlideshowShuffle": "Shuffle",
"settingsSlideshowTransitionTile": "Transition",
"settingsSlideshowTransitionTitle": "Transition",
"settingsSlideshowIntervalTile": "Interval",
"settingsSlideshowIntervalTitle": "Interval",
"settingsSlideshowVideoPlaybackTile": "Video playback",
"settingsSlideshowVideoPlaybackTitle": "Video Playback",
"settingsVideoPageTitle": "Video Settings", "settingsVideoPageTitle": "Video Settings",
"settingsSectionVideo": "Video", "settingsSectionVideo": "Video",
"settingsVideoShowVideos": "Show videos", "settingsVideoShowVideos": "Show videos",
@ -723,6 +753,7 @@
"settingsSectionDisplay": "Display", "settingsSectionDisplay": "Display",
"settingsThemeBrightness": "Theme", "settingsThemeBrightness": "Theme",
"settingsThemeColorHighlights": "Color highlights", "settingsThemeColorHighlights": "Color highlights",
"settingsThemeEnableDynamicColor": "Dynamic color",
"settingsDisplayRefreshRateModeTile": "Display refresh rate", "settingsDisplayRefreshRateModeTile": "Display refresh rate",
"settingsDisplayRefreshRateModeTitle": "Refresh Rate", "settingsDisplayRefreshRateModeTitle": "Refresh Rate",
@ -745,6 +776,7 @@
"statsTopTags": "Top Tags", "statsTopTags": "Top Tags",
"viewerOpenPanoramaButtonLabel": "OPEN PANORAMA", "viewerOpenPanoramaButtonLabel": "OPEN PANORAMA",
"viewerSetWallpaperButtonLabel": "SET WALLPAPER",
"viewerErrorUnknown": "Oops!", "viewerErrorUnknown": "Oops!",
"viewerErrorDoesNotExist": "The file no longer exists.", "viewerErrorDoesNotExist": "The file no longer exists.",

View file

@ -50,6 +50,7 @@
"entryActionDelete": "Borrar", "entryActionDelete": "Borrar",
"entryActionConvert": "Convertir", "entryActionConvert": "Convertir",
"entryActionExport": "Exportar", "entryActionExport": "Exportar",
"entryActionInfo": "Información",
"entryActionRename": "Renombrar", "entryActionRename": "Renombrar",
"entryActionRestore": "Restaurar", "entryActionRestore": "Restaurar",
"entryActionRotateCCW": "Rotar en sentido antihorario", "entryActionRotateCCW": "Rotar en sentido antihorario",
@ -80,6 +81,9 @@
"videoActionSetSpeed": "Velocidad de reproducción", "videoActionSetSpeed": "Velocidad de reproducción",
"videoActionSettings": "Ajustes", "videoActionSettings": "Ajustes",
"slideshowActionResume": "Reanudar",
"slideshowActionShowInCollection": "Mostrar en Colección",
"entryInfoActionEditDate": "Editar fecha y hora", "entryInfoActionEditDate": "Editar fecha y hora",
"entryInfoActionEditLocation": "Editar ubicación", "entryInfoActionEditLocation": "Editar ubicación",
"entryInfoActionEditRating": "Editar clasificación", "entryInfoActionEditRating": "Editar clasificación",
@ -144,10 +148,23 @@
"displayRefreshRatePreferHighest": "Alta tasa", "displayRefreshRatePreferHighest": "Alta tasa",
"displayRefreshRatePreferLowest": "Baja tasa", "displayRefreshRatePreferLowest": "Baja tasa",
"slideshowVideoPlaybackSkip": "Saltear",
"slideshowVideoPlaybackMuted": "Reproducir sin sonido",
"slideshowVideoPlaybackWithSound": "Reproducir con sonido",
"themeBrightnessLight": "Claro", "themeBrightnessLight": "Claro",
"themeBrightnessDark": "Obscuro", "themeBrightnessDark": "Obscuro",
"themeBrightnessBlack": "Negro", "themeBrightnessBlack": "Negro",
"viewerTransitionSlide": "Diapositiva",
"viewerTransitionParallax": "Paralaje",
"viewerTransitionFade": "Desvanecer",
"viewerTransitionZoomIn": "Acercar",
"wallpaperTargetHome": "Pantalla de inicio",
"wallpaperTargetLock": "Pantalla de bloqueo",
"wallpaperTargetHomeLock": "Pantallas de inicio y bloqueo",
"albumTierNew": "Nuevo", "albumTierNew": "Nuevo",
"albumTierPinned": "Fijado", "albumTierPinned": "Fijado",
"albumTierSpecial": "Común", "albumTierSpecial": "Común",
@ -262,6 +279,7 @@
"menuActionSelectAll": "Seleccionar todo", "menuActionSelectAll": "Seleccionar todo",
"menuActionSelectNone": "Deseleccionar", "menuActionSelectNone": "Deseleccionar",
"menuActionMap": "Mapa", "menuActionMap": "Mapa",
"menuActionSlideshow": "Presentación",
"menuActionStats": "Estadísticas", "menuActionStats": "Estadísticas",
"viewDialogTabSort": "Ordenar", "viewDialogTabSort": "Ordenar",
@ -278,7 +296,6 @@
"appPickDialogTitle": "Escoger aplicación", "appPickDialogTitle": "Escoger aplicación",
"appPickDialogNone": "Ninguna", "appPickDialogNone": "Ninguna",
"aboutPageTitle": "Acerca de", "aboutPageTitle": "Acerca de",
"aboutLinkSources": "Fuentes", "aboutLinkSources": "Fuentes",
"aboutLinkLicense": "Licencia", "aboutLinkLicense": "Licencia",
@ -296,7 +313,6 @@
"aboutCreditsWorldAtlas1": "Esta aplicación usa un archivo TopoJSON de", "aboutCreditsWorldAtlas1": "Esta aplicación usa un archivo TopoJSON de",
"aboutCreditsWorldAtlas2": "bajo licencia ISC.", "aboutCreditsWorldAtlas2": "bajo licencia ISC.",
"aboutCreditsTranslators": "Traductores:", "aboutCreditsTranslators": "Traductores:",
"aboutCreditsTranslatorLine": "{language}: {names}",
"aboutLicenses": "Licencias de código abierto", "aboutLicenses": "Licencias de código abierto",
"aboutLicensesBanner": "Esta aplicación usa los siguientes paquetes y librerías de código abierto.", "aboutLicensesBanner": "Esta aplicación usa los siguientes paquetes y librerías de código abierto.",
@ -351,6 +367,7 @@
"collectionEmptyFavourites": "Sin favoritos", "collectionEmptyFavourites": "Sin favoritos",
"collectionEmptyVideos": "Sin videos", "collectionEmptyVideos": "Sin videos",
"collectionEmptyImages": "Sin imágenes", "collectionEmptyImages": "Sin imágenes",
"collectionEmptyGrantAccessButtonLabel": "Otorgar accceso",
"collectionSelectSectionTooltip": "Seleccionar sección", "collectionSelectSectionTooltip": "Seleccionar sección",
"collectionDeselectSectionTooltip": "Deseleccionar sección", "collectionDeselectSectionTooltip": "Deseleccionar sección",
@ -421,6 +438,7 @@
"settingsSectionNavigation": "Navegación", "settingsSectionNavigation": "Navegación",
"settingsHome": "Inicio", "settingsHome": "Inicio",
"settingsShowBottomNavigationBar": "Mostrar barra de navegación inferior",
"settingsKeepScreenOnTile": "Mantener pantalla encendida", "settingsKeepScreenOnTile": "Mantener pantalla encendida",
"settingsKeepScreenOnTitle": "Mantener pantalla encendida", "settingsKeepScreenOnTitle": "Mantener pantalla encendida",
"settingsDoubleBackExit": "Presione «atrás» dos veces para salir", "settingsDoubleBackExit": "Presione «atrás» dos veces para salir",
@ -443,6 +461,7 @@
"settingsThumbnailOverlayTile": "Incrustaciones", "settingsThumbnailOverlayTile": "Incrustaciones",
"settingsThumbnailOverlayTitle": "Incrustaciones", "settingsThumbnailOverlayTitle": "Incrustaciones",
"settingsThumbnailShowFavouriteIcon": "Mostrar icono de favoritos", "settingsThumbnailShowFavouriteIcon": "Mostrar icono de favoritos",
"settingsThumbnailShowTagIcon": "Mostrar ícono de etiqueta",
"settingsThumbnailShowLocationIcon": "Mostrar icono de ubicación", "settingsThumbnailShowLocationIcon": "Mostrar icono de ubicación",
"settingsThumbnailShowMotionPhotoIcon": "Mostrar icono de foto en movimiento", "settingsThumbnailShowMotionPhotoIcon": "Mostrar icono de foto en movimiento",
"settingsThumbnailShowRating": "Mostrar clasificación", "settingsThumbnailShowRating": "Mostrar clasificación",
@ -476,9 +495,20 @@
"settingsViewerShowInformation": "Mostrar información", "settingsViewerShowInformation": "Mostrar información",
"settingsViewerShowInformationSubtitle": "Mostrar título, fecha, ubicación, etc.", "settingsViewerShowInformationSubtitle": "Mostrar título, fecha, ubicación, etc.",
"settingsViewerShowShootingDetails": "Mostrar detalles de toma", "settingsViewerShowShootingDetails": "Mostrar detalles de toma",
"settingsViewerShowOverlayThumbnails": "Mostrar miniaturas", "settingsViewerShowOverlayThumbnails": "Mostrar miniaturas",
"settingsViewerEnableOverlayBlurEffect": "Efecto de difuminado", "settingsViewerEnableOverlayBlurEffect": "Efecto de difuminado",
"settingsViewerSlideshowTile": "Presentación",
"settingsViewerSlideshowTitle": "Presentación",
"settingsSlideshowRepeat": "Repetir",
"settingsSlideshowShuffle": "Mezclar",
"settingsSlideshowTransitionTile": "Transición",
"settingsSlideshowTransitionTitle": "Transición",
"settingsSlideshowIntervalTile": "Intervalo",
"settingsSlideshowIntervalTitle": "Intervalo",
"settingsSlideshowVideoPlaybackTile": "Reproducción de video",
"settingsSlideshowVideoPlaybackTitle": "Reproducción de video",
"settingsVideoPageTitle": "Ajustes de video", "settingsVideoPageTitle": "Ajustes de video",
"settingsSectionVideo": "Video", "settingsSectionVideo": "Video",
"settingsVideoShowVideos": "Mostrar videos", "settingsVideoShowVideos": "Mostrar videos",
@ -486,8 +516,6 @@
"settingsVideoEnableAutoPlay": "Reproducción automática", "settingsVideoEnableAutoPlay": "Reproducción automática",
"settingsVideoLoopModeTile": "Modo bucle", "settingsVideoLoopModeTile": "Modo bucle",
"settingsVideoLoopModeTitle": "Modo bucle", "settingsVideoLoopModeTitle": "Modo bucle",
"settingsVideoQuickActionsTile": "Acciones rápidas para videos",
"settingsVideoQuickActionEditorTitle": "Acciones rápidas",
"settingsSubtitleThemeTile": "Subtítulos", "settingsSubtitleThemeTile": "Subtítulos",
"settingsSubtitleThemeTitle": "Subtítulos", "settingsSubtitleThemeTitle": "Subtítulos",
@ -545,6 +573,7 @@
"settingsSectionDisplay": "Pantalla", "settingsSectionDisplay": "Pantalla",
"settingsThemeBrightness": "Tema", "settingsThemeBrightness": "Tema",
"settingsThemeColorHighlights": "Acentos de color", "settingsThemeColorHighlights": "Acentos de color",
"settingsThemeEnableDynamicColor": "Color dinámico",
"settingsDisplayRefreshRateModeTile": "Tasa de refresco de la pantalla", "settingsDisplayRefreshRateModeTile": "Tasa de refresco de la pantalla",
"settingsDisplayRefreshRateModeTitle": "Tasa de refresco", "settingsDisplayRefreshRateModeTitle": "Tasa de refresco",
@ -562,6 +591,7 @@
"statsTopTags": "Etiquetas principales", "statsTopTags": "Etiquetas principales",
"viewerOpenPanoramaButtonLabel": "ABRIR PANORÁMICA", "viewerOpenPanoramaButtonLabel": "ABRIR PANORÁMICA",
"viewerSetWallpaperButtonLabel": "ESTABLECER FONDO",
"viewerErrorUnknown": "¡Ups!", "viewerErrorUnknown": "¡Ups!",
"viewerErrorDoesNotExist": "El archivo no existe.", "viewerErrorDoesNotExist": "El archivo no existe.",

View file

@ -50,6 +50,7 @@
"entryActionDelete": "Supprimer", "entryActionDelete": "Supprimer",
"entryActionConvert": "Convertir", "entryActionConvert": "Convertir",
"entryActionExport": "Exporter", "entryActionExport": "Exporter",
"entryActionInfo": "Détails",
"entryActionRename": "Renommer", "entryActionRename": "Renommer",
"entryActionRestore": "Restaurer", "entryActionRestore": "Restaurer",
"entryActionRotateCCW": "Pivoter à gauche", "entryActionRotateCCW": "Pivoter à gauche",
@ -80,6 +81,9 @@
"videoActionSetSpeed": "Vitesse de lecture", "videoActionSetSpeed": "Vitesse de lecture",
"videoActionSettings": "Préférences", "videoActionSettings": "Préférences",
"slideshowActionResume": "Reprendre",
"slideshowActionShowInCollection": "Afficher dans Collection",
"entryInfoActionEditDate": "Modifier la date", "entryInfoActionEditDate": "Modifier la date",
"entryInfoActionEditLocation": "Modifier le lieu", "entryInfoActionEditLocation": "Modifier le lieu",
"entryInfoActionEditRating": "Modifier la notation", "entryInfoActionEditRating": "Modifier la notation",
@ -144,10 +148,23 @@
"displayRefreshRatePreferHighest": "Fréquence maximale", "displayRefreshRatePreferHighest": "Fréquence maximale",
"displayRefreshRatePreferLowest": "Fréquence minimale", "displayRefreshRatePreferLowest": "Fréquence minimale",
"slideshowVideoPlaybackSkip": "Passer",
"slideshowVideoPlaybackMuted": "Jouer sans son",
"slideshowVideoPlaybackWithSound": "Jouer avec son",
"themeBrightnessLight": "Clair", "themeBrightnessLight": "Clair",
"themeBrightnessDark": "Sombre", "themeBrightnessDark": "Sombre",
"themeBrightnessBlack": "Noir", "themeBrightnessBlack": "Noir",
"viewerTransitionSlide": "Défilement",
"viewerTransitionParallax": "Parallaxe",
"viewerTransitionFade": "Fondu",
"viewerTransitionZoomIn": "Zoom",
"wallpaperTargetHome": "Écran daccueil",
"wallpaperTargetLock": "Écran de verrouillage",
"wallpaperTargetHomeLock": "Écrans accueil et verrouillage",
"albumTierNew": "Nouveaux", "albumTierNew": "Nouveaux",
"albumTierPinned": "Épinglés", "albumTierPinned": "Épinglés",
"albumTierSpecial": "Standards", "albumTierSpecial": "Standards",
@ -262,6 +279,7 @@
"menuActionSelectAll": "Tout sélectionner", "menuActionSelectAll": "Tout sélectionner",
"menuActionSelectNone": "Tout désélectionner", "menuActionSelectNone": "Tout désélectionner",
"menuActionMap": "Carte", "menuActionMap": "Carte",
"menuActionSlideshow": "Diaporama",
"menuActionStats": "Statistiques", "menuActionStats": "Statistiques",
"viewDialogTabSort": "Tri", "viewDialogTabSort": "Tri",
@ -349,6 +367,7 @@
"collectionEmptyFavourites": "Aucun favori", "collectionEmptyFavourites": "Aucun favori",
"collectionEmptyVideos": "Aucune vidéo", "collectionEmptyVideos": "Aucune vidéo",
"collectionEmptyImages": "Aucune image", "collectionEmptyImages": "Aucune image",
"collectionEmptyGrantAccessButtonLabel": "Autoriser laccès",
"collectionSelectSectionTooltip": "Sélectionner la section", "collectionSelectSectionTooltip": "Sélectionner la section",
"collectionDeselectSectionTooltip": "Désélectionner la section", "collectionDeselectSectionTooltip": "Désélectionner la section",
@ -479,6 +498,17 @@
"settingsViewerShowOverlayThumbnails": "Afficher les vignettes", "settingsViewerShowOverlayThumbnails": "Afficher les vignettes",
"settingsViewerEnableOverlayBlurEffect": "Effets de flou", "settingsViewerEnableOverlayBlurEffect": "Effets de flou",
"settingsViewerSlideshowTile": "Diaporama",
"settingsViewerSlideshowTitle": "Diaporama",
"settingsSlideshowRepeat": "Répéter",
"settingsSlideshowShuffle": "Aléatoire",
"settingsSlideshowTransitionTile": "Transition",
"settingsSlideshowTransitionTitle": "Transition",
"settingsSlideshowIntervalTile": "Intervalle",
"settingsSlideshowIntervalTitle": "Intervalle",
"settingsSlideshowVideoPlaybackTile": "Lecture de vidéos",
"settingsSlideshowVideoPlaybackTitle": "Lecture de vidéos",
"settingsVideoPageTitle": "Réglages vidéo", "settingsVideoPageTitle": "Réglages vidéo",
"settingsSectionVideo": "Vidéo", "settingsSectionVideo": "Vidéo",
"settingsVideoShowVideos": "Afficher les vidéos", "settingsVideoShowVideos": "Afficher les vidéos",
@ -543,6 +573,7 @@
"settingsSectionDisplay": "Affichage", "settingsSectionDisplay": "Affichage",
"settingsThemeBrightness": "Thème", "settingsThemeBrightness": "Thème",
"settingsThemeColorHighlights": "Surlignages colorés", "settingsThemeColorHighlights": "Surlignages colorés",
"settingsThemeEnableDynamicColor": "Couleur dynamique",
"settingsDisplayRefreshRateModeTile": "Fréquence dactualisation de l'écran", "settingsDisplayRefreshRateModeTile": "Fréquence dactualisation de l'écran",
"settingsDisplayRefreshRateModeTitle": "Fréquence dactualisation", "settingsDisplayRefreshRateModeTitle": "Fréquence dactualisation",
@ -560,6 +591,7 @@
"statsTopTags": "Top libellés", "statsTopTags": "Top libellés",
"viewerOpenPanoramaButtonLabel": "OUVRIR LE PANORAMA", "viewerOpenPanoramaButtonLabel": "OUVRIR LE PANORAMA",
"viewerSetWallpaperButtonLabel": "APPLIQUER",
"viewerErrorUnknown": "Zut !", "viewerErrorUnknown": "Zut !",
"viewerErrorDoesNotExist": "Le fichier nexiste plus.", "viewerErrorDoesNotExist": "Le fichier nexiste plus.",

View file

@ -50,6 +50,7 @@
"entryActionDelete": "Hapus", "entryActionDelete": "Hapus",
"entryActionConvert": "Ubah", "entryActionConvert": "Ubah",
"entryActionExport": "Ekspor", "entryActionExport": "Ekspor",
"entryActionInfo": "Info",
"entryActionRename": "Ganti nama", "entryActionRename": "Ganti nama",
"entryActionRestore": "Pulihkan", "entryActionRestore": "Pulihkan",
"entryActionRotateCCW": "Putar berlawanan arah jarum jam", "entryActionRotateCCW": "Putar berlawanan arah jarum jam",

View file

@ -50,6 +50,7 @@
"entryActionDelete": "Elimina", "entryActionDelete": "Elimina",
"entryActionConvert": "Converti", "entryActionConvert": "Converti",
"entryActionExport": "Esportazione", "entryActionExport": "Esportazione",
"entryActionInfo": "Info",
"entryActionRename": "Rinomina", "entryActionRename": "Rinomina",
"entryActionRestore": "Ripristina", "entryActionRestore": "Ripristina",
"entryActionRotateCCW": "Ruota in senso antiorario", "entryActionRotateCCW": "Ruota in senso antiorario",
@ -80,6 +81,9 @@
"videoActionSetSpeed": "Velocità di riproduzione", "videoActionSetSpeed": "Velocità di riproduzione",
"videoActionSettings": "Impostazioni", "videoActionSettings": "Impostazioni",
"slideshowActionResume": "Riprendi",
"slideshowActionShowInCollection": "Mostra nella Collezione",
"entryInfoActionEditDate": "Modifica data e ora", "entryInfoActionEditDate": "Modifica data e ora",
"entryInfoActionEditLocation": "Modifica posizione", "entryInfoActionEditLocation": "Modifica posizione",
"entryInfoActionEditRating": "Modifica valutazione", "entryInfoActionEditRating": "Modifica valutazione",
@ -144,10 +148,23 @@
"displayRefreshRatePreferHighest": "Frequenza massima", "displayRefreshRatePreferHighest": "Frequenza massima",
"displayRefreshRatePreferLowest": "Frequenza minima", "displayRefreshRatePreferLowest": "Frequenza minima",
"slideshowVideoPlaybackSkip": "Salta",
"slideshowVideoPlaybackMuted": "Riproduci senza audio",
"slideshowVideoPlaybackWithSound": "Riproduci con audio",
"themeBrightnessLight": "Chiaro", "themeBrightnessLight": "Chiaro",
"themeBrightnessDark": "Scuro", "themeBrightnessDark": "Scuro",
"themeBrightnessBlack": "Nero", "themeBrightnessBlack": "Nero",
"viewerTransitionSlide": "Diapositiva",
"viewerTransitionParallax": "Parallasse",
"viewerTransitionFade": "Dissolvenza",
"viewerTransitionZoomIn": "Ingrandisci",
"wallpaperTargetHome": "Schermata iniziale",
"wallpaperTargetLock": "Schermata di blocco",
"wallpaperTargetHomeLock": "Schermata iniziale e di blocco",
"albumTierNew": "Nuovi", "albumTierNew": "Nuovi",
"albumTierPinned": "Fissati", "albumTierPinned": "Fissati",
"albumTierSpecial": "Frequenti", "albumTierSpecial": "Frequenti",
@ -262,6 +279,7 @@
"menuActionSelectAll": "Seleziona tutto", "menuActionSelectAll": "Seleziona tutto",
"menuActionSelectNone": "Deseleziona tutto", "menuActionSelectNone": "Deseleziona tutto",
"menuActionMap": "Mappa", "menuActionMap": "Mappa",
"menuActionSlideshow": "Presentazione",
"menuActionStats": "Statistiche", "menuActionStats": "Statistiche",
"viewDialogTabSort": "Ordina", "viewDialogTabSort": "Ordina",
@ -349,6 +367,7 @@
"collectionEmptyFavourites": "Nessun preferito", "collectionEmptyFavourites": "Nessun preferito",
"collectionEmptyVideos": "Nessun video", "collectionEmptyVideos": "Nessun video",
"collectionEmptyImages": "Nessuna immagine", "collectionEmptyImages": "Nessuna immagine",
"collectionEmptyGrantAccessButtonLabel": "Consenti accesso",
"collectionSelectSectionTooltip": "Seleziona sezione", "collectionSelectSectionTooltip": "Seleziona sezione",
"collectionDeselectSectionTooltip": "Deseleziona sezione", "collectionDeselectSectionTooltip": "Deseleziona sezione",
@ -479,6 +498,17 @@
"settingsViewerShowOverlayThumbnails": "Mostra le miniature", "settingsViewerShowOverlayThumbnails": "Mostra le miniature",
"settingsViewerEnableOverlayBlurEffect": "Effetto sfocatura", "settingsViewerEnableOverlayBlurEffect": "Effetto sfocatura",
"settingsViewerSlideshowTile": "Presentazione",
"settingsViewerSlideshowTitle": "Presentazione",
"settingsSlideshowRepeat": "Ripeti",
"settingsSlideshowShuffle": "Ordine casuale",
"settingsSlideshowTransitionTile": "Transizione",
"settingsSlideshowTransitionTitle": "Transizione",
"settingsSlideshowIntervalTile": "Intervallo",
"settingsSlideshowIntervalTitle": "Intervallo",
"settingsSlideshowVideoPlaybackTile": "Riproduzione video",
"settingsSlideshowVideoPlaybackTitle": "Riproduzione video",
"settingsVideoPageTitle": "Impostazioni video", "settingsVideoPageTitle": "Impostazioni video",
"settingsSectionVideo": "Video", "settingsSectionVideo": "Video",
"settingsVideoShowVideos": "Mostra video", "settingsVideoShowVideos": "Mostra video",
@ -543,6 +573,7 @@
"settingsSectionDisplay": "Schermo", "settingsSectionDisplay": "Schermo",
"settingsThemeBrightness": "Tema", "settingsThemeBrightness": "Tema",
"settingsThemeColorHighlights": "Colori evidenziati", "settingsThemeColorHighlights": "Colori evidenziati",
"settingsThemeEnableDynamicColor": "Colori dinamici",
"settingsDisplayRefreshRateModeTile": "Frequenza di aggiornamento dello schermo", "settingsDisplayRefreshRateModeTile": "Frequenza di aggiornamento dello schermo",
"settingsDisplayRefreshRateModeTitle": "Frequenza di aggiornamento", "settingsDisplayRefreshRateModeTitle": "Frequenza di aggiornamento",
@ -560,6 +591,7 @@
"statsTopTags": "Etichette più frequenti", "statsTopTags": "Etichette più frequenti",
"viewerOpenPanoramaButtonLabel": "APRI PANORAMA", "viewerOpenPanoramaButtonLabel": "APRI PANORAMA",
"viewerSetWallpaperButtonLabel": "IMPOSTA SFONDO",
"viewerErrorUnknown": "Ops!", "viewerErrorUnknown": "Ops!",
"viewerErrorDoesNotExist": "Il file non esiste più", "viewerErrorDoesNotExist": "Il file non esiste più",

View file

@ -50,6 +50,7 @@
"entryActionDelete": "削除", "entryActionDelete": "削除",
"entryActionConvert": "変換", "entryActionConvert": "変換",
"entryActionExport": "エクスポート", "entryActionExport": "エクスポート",
"entryActionInfo": "情報",
"entryActionRename": "名前を変更", "entryActionRename": "名前を変更",
"entryActionRestore": "元に戻す", "entryActionRestore": "元に戻す",
"entryActionRotateCCW": "反時計回りに回転", "entryActionRotateCCW": "反時計回りに回転",

View file

@ -50,6 +50,7 @@
"entryActionDelete": "삭제", "entryActionDelete": "삭제",
"entryActionConvert": "변환", "entryActionConvert": "변환",
"entryActionExport": "내보내기", "entryActionExport": "내보내기",
"entryActionInfo": "상세정보",
"entryActionRename": "이름 변경", "entryActionRename": "이름 변경",
"entryActionRestore": "복원", "entryActionRestore": "복원",
"entryActionRotateCCW": "좌회전", "entryActionRotateCCW": "좌회전",
@ -80,6 +81,9 @@
"videoActionSetSpeed": "재생 배속", "videoActionSetSpeed": "재생 배속",
"videoActionSettings": "설정", "videoActionSettings": "설정",
"slideshowActionResume": "이어서",
"slideshowActionShowInCollection": "미디어 페이지에서 보기",
"entryInfoActionEditDate": "날짜 및 시간 수정", "entryInfoActionEditDate": "날짜 및 시간 수정",
"entryInfoActionEditLocation": "위치 수정", "entryInfoActionEditLocation": "위치 수정",
"entryInfoActionEditRating": "별점 수정", "entryInfoActionEditRating": "별점 수정",
@ -144,10 +148,23 @@
"displayRefreshRatePreferHighest": "가장 높은 재생률", "displayRefreshRatePreferHighest": "가장 높은 재생률",
"displayRefreshRatePreferLowest": "가장 낮은 재생률", "displayRefreshRatePreferLowest": "가장 낮은 재생률",
"slideshowVideoPlaybackSkip": "생략",
"slideshowVideoPlaybackMuted": "음소거 재생",
"slideshowVideoPlaybackWithSound": "일반 재생",
"themeBrightnessLight": "라이트", "themeBrightnessLight": "라이트",
"themeBrightnessDark": "다크", "themeBrightnessDark": "다크",
"themeBrightnessBlack": "검은색", "themeBrightnessBlack": "검은색",
"viewerTransitionSlide": "좌우",
"viewerTransitionParallax": "시차",
"viewerTransitionFade": "페이드",
"viewerTransitionZoomIn": "확대",
"wallpaperTargetHome": "홈 화면",
"wallpaperTargetLock": "잠금화면",
"wallpaperTargetHomeLock": "홈 및 잠금화면",
"albumTierNew": "신규", "albumTierNew": "신규",
"albumTierPinned": "고정", "albumTierPinned": "고정",
"albumTierSpecial": "기본", "albumTierSpecial": "기본",
@ -262,6 +279,7 @@
"menuActionSelectAll": "모두 선택", "menuActionSelectAll": "모두 선택",
"menuActionSelectNone": "모두 해제", "menuActionSelectNone": "모두 해제",
"menuActionMap": "지도", "menuActionMap": "지도",
"menuActionSlideshow": "슬라이드쇼",
"menuActionStats": "통계", "menuActionStats": "통계",
"viewDialogTabSort": "정렬", "viewDialogTabSort": "정렬",
@ -349,6 +367,7 @@
"collectionEmptyFavourites": "즐겨찾기가 없습니다", "collectionEmptyFavourites": "즐겨찾기가 없습니다",
"collectionEmptyVideos": "동영상이 없습니다", "collectionEmptyVideos": "동영상이 없습니다",
"collectionEmptyImages": "사진이 없습니다", "collectionEmptyImages": "사진이 없습니다",
"collectionEmptyGrantAccessButtonLabel": "접근 허용",
"collectionSelectSectionTooltip": "묶음 선택", "collectionSelectSectionTooltip": "묶음 선택",
"collectionDeselectSectionTooltip": "묶음 선택 해제", "collectionDeselectSectionTooltip": "묶음 선택 해제",
@ -479,6 +498,17 @@
"settingsViewerShowOverlayThumbnails": "섬네일 표시", "settingsViewerShowOverlayThumbnails": "섬네일 표시",
"settingsViewerEnableOverlayBlurEffect": "흐림 효과", "settingsViewerEnableOverlayBlurEffect": "흐림 효과",
"settingsViewerSlideshowTile": "슬라이드쇼",
"settingsViewerSlideshowTitle": "슬라이드쇼",
"settingsSlideshowRepeat": "반복",
"settingsSlideshowShuffle": "순서섞기",
"settingsSlideshowTransitionTile": "전환 효과",
"settingsSlideshowTransitionTitle": "전환 효과",
"settingsSlideshowIntervalTile": "교체 주기",
"settingsSlideshowIntervalTitle": "교체 주기",
"settingsSlideshowVideoPlaybackTile": "동영상 재생",
"settingsSlideshowVideoPlaybackTitle": "동영상 재생",
"settingsVideoPageTitle": "동영상 설정", "settingsVideoPageTitle": "동영상 설정",
"settingsSectionVideo": "동영상", "settingsSectionVideo": "동영상",
"settingsVideoShowVideos": "미디어에 동영상 표시", "settingsVideoShowVideos": "미디어에 동영상 표시",
@ -543,6 +573,7 @@
"settingsSectionDisplay": "디스플레이", "settingsSectionDisplay": "디스플레이",
"settingsThemeBrightness": "테마", "settingsThemeBrightness": "테마",
"settingsThemeColorHighlights": "색 강조", "settingsThemeColorHighlights": "색 강조",
"settingsThemeEnableDynamicColor": "동적 색상",
"settingsDisplayRefreshRateModeTile": "화면 재생률", "settingsDisplayRefreshRateModeTile": "화면 재생률",
"settingsDisplayRefreshRateModeTitle": "화면 재생률", "settingsDisplayRefreshRateModeTitle": "화면 재생률",
@ -560,6 +591,7 @@
"statsTopTags": "태그 랭킹", "statsTopTags": "태그 랭킹",
"viewerOpenPanoramaButtonLabel": "파노라마 열기", "viewerOpenPanoramaButtonLabel": "파노라마 열기",
"viewerSetWallpaperButtonLabel": "설정",
"viewerErrorUnknown": "아이구!", "viewerErrorUnknown": "아이구!",
"viewerErrorDoesNotExist": "파일이 존재하지 않습니다.", "viewerErrorDoesNotExist": "파일이 존재하지 않습니다.",

View file

@ -49,6 +49,7 @@
"entryActionCopyToClipboard": "Copiar para área de transferência", "entryActionCopyToClipboard": "Copiar para área de transferência",
"entryActionDelete": "Excluir", "entryActionDelete": "Excluir",
"entryActionExport": "Exportar", "entryActionExport": "Exportar",
"entryActionInfo": "Informações",
"entryActionConvert": "Converter", "entryActionConvert": "Converter",
"entryActionRename": "Renomear", "entryActionRename": "Renomear",
"entryActionRestore": "Restaurar", "entryActionRestore": "Restaurar",
@ -80,6 +81,9 @@
"videoActionSetSpeed": "Velocidade de reprodução", "videoActionSetSpeed": "Velocidade de reprodução",
"videoActionSettings": "Configurações", "videoActionSettings": "Configurações",
"slideshowActionResume": "Retomar",
"slideshowActionShowInCollection": "Mostrar na Coleção",
"entryInfoActionEditDate": "Editar data e hora", "entryInfoActionEditDate": "Editar data e hora",
"entryInfoActionEditLocation": "Editar localização", "entryInfoActionEditLocation": "Editar localização",
"entryInfoActionEditRating": "Editar classificação", "entryInfoActionEditRating": "Editar classificação",
@ -144,10 +148,23 @@
"displayRefreshRatePreferHighest": "Taxa mais alta", "displayRefreshRatePreferHighest": "Taxa mais alta",
"displayRefreshRatePreferLowest": "Taxa mais baixa", "displayRefreshRatePreferLowest": "Taxa mais baixa",
"slideshowVideoPlaybackSkip": "Pular",
"slideshowVideoPlaybackMuted": "Reproduzir sem som",
"slideshowVideoPlaybackWithSound": "Reproduzir com som",
"themeBrightnessLight": "Claro", "themeBrightnessLight": "Claro",
"themeBrightnessDark": "Escuro", "themeBrightnessDark": "Escuro",
"themeBrightnessBlack": "Preto", "themeBrightnessBlack": "Preto",
"viewerTransitionSlide": "Deslizar",
"viewerTransitionParallax": "Parallax",
"viewerTransitionFade": "Desvaneça",
"viewerTransitionZoomIn": "Mais zoom",
"wallpaperTargetHome": "Tela inicial",
"wallpaperTargetLock": "Tela de bloqueio",
"wallpaperTargetHomeLock": "Telas iniciais e de bloqueio",
"albumTierNew": "Novo", "albumTierNew": "Novo",
"albumTierPinned": "Fixada", "albumTierPinned": "Fixada",
"albumTierSpecial": "Comum", "albumTierSpecial": "Comum",
@ -262,6 +279,7 @@
"menuActionSelectAll": "Selecionar tudo", "menuActionSelectAll": "Selecionar tudo",
"menuActionSelectNone": "Selecione nenhum", "menuActionSelectNone": "Selecione nenhum",
"menuActionMap": "Mapa", "menuActionMap": "Mapa",
"menuActionSlideshow": "Apresentação de slides",
"menuActionStats": "Estatísticas", "menuActionStats": "Estatísticas",
"viewDialogTabSort": "Organizar", "viewDialogTabSort": "Organizar",
@ -349,6 +367,7 @@
"collectionEmptyFavourites": "Nenhum favorito", "collectionEmptyFavourites": "Nenhum favorito",
"collectionEmptyVideos": "Nenhum video", "collectionEmptyVideos": "Nenhum video",
"collectionEmptyImages": "Nenhuma image", "collectionEmptyImages": "Nenhuma image",
"collectionEmptyGrantAccessButtonLabel": "Garantir acesso",
"collectionSelectSectionTooltip": "Selecionar seção", "collectionSelectSectionTooltip": "Selecionar seção",
"collectionDeselectSectionTooltip": "Desmarcar seção", "collectionDeselectSectionTooltip": "Desmarcar seção",
@ -479,6 +498,17 @@
"settingsViewerShowOverlayThumbnails": "Mostrar miniaturas", "settingsViewerShowOverlayThumbnails": "Mostrar miniaturas",
"settingsViewerEnableOverlayBlurEffect": "Efeito de desfoque", "settingsViewerEnableOverlayBlurEffect": "Efeito de desfoque",
"settingsViewerSlideshowTile": "Apresentação de slides",
"settingsViewerSlideshowTitle": "Apresentação de slides",
"settingsSlideshowRepeat": "Repetir",
"settingsSlideshowShuffle": "Embaralhar",
"settingsSlideshowTransitionTile": "Transição",
"settingsSlideshowTransitionTitle": "Transição",
"settingsSlideshowIntervalTile": "Intervalo",
"settingsSlideshowIntervalTitle": "Intervalo",
"settingsSlideshowVideoPlaybackTile": "Reprodução de vídeo",
"settingsSlideshowVideoPlaybackTitle": "Reprodução de vídeo",
"settingsVideoPageTitle": "Configurações de vídeo", "settingsVideoPageTitle": "Configurações de vídeo",
"settingsSectionVideo": "Vídeo", "settingsSectionVideo": "Vídeo",
"settingsVideoShowVideos": "Mostrar vídeos", "settingsVideoShowVideos": "Mostrar vídeos",
@ -543,6 +573,7 @@
"settingsSectionDisplay": "Tela", "settingsSectionDisplay": "Tela",
"settingsThemeBrightness": "Tema", "settingsThemeBrightness": "Tema",
"settingsThemeColorHighlights": "Destaques de cores", "settingsThemeColorHighlights": "Destaques de cores",
"settingsThemeEnableDynamicColor": "Cor dinâmica",
"settingsDisplayRefreshRateModeTile": "Taxa de atualização de exibição", "settingsDisplayRefreshRateModeTile": "Taxa de atualização de exibição",
"settingsDisplayRefreshRateModeTitle": "Taxa de atualização", "settingsDisplayRefreshRateModeTitle": "Taxa de atualização",
@ -560,6 +591,7 @@
"statsTopTags": "Principais Etiquetas", "statsTopTags": "Principais Etiquetas",
"viewerOpenPanoramaButtonLabel": "ABRIR PANORAMA", "viewerOpenPanoramaButtonLabel": "ABRIR PANORAMA",
"viewerSetWallpaperButtonLabel": "DEFINIR PAPEL DE PAREDE",
"viewerErrorUnknown": "Algo não está certo!", "viewerErrorUnknown": "Algo não está certo!",
"viewerErrorDoesNotExist": "O arquivo não existe mais.", "viewerErrorDoesNotExist": "O arquivo não existe mais.",

View file

@ -50,6 +50,7 @@
"entryActionDelete": "Удалить", "entryActionDelete": "Удалить",
"entryActionConvert": "Конвертировать", "entryActionConvert": "Конвертировать",
"entryActionExport": "Экспорт", "entryActionExport": "Экспорт",
"entryActionInfo": "Информация",
"entryActionRename": "Переименовать", "entryActionRename": "Переименовать",
"entryActionRestore": "Восстановить", "entryActionRestore": "Восстановить",
"entryActionRotateCCW": "Повернуть против часовой стрелки", "entryActionRotateCCW": "Повернуть против часовой стрелки",

639
lib/l10n/app_tr.arb Normal file
View file

@ -0,0 +1,639 @@
{
"appName": "Aves",
"welcomeMessage": "Aves'e Hoş Geldiniz",
"welcomeOptional": "İsteğe bağlı",
"welcomeTermsToggle": "Hüküm ve koşulları kabul ediyorum",
"itemCount": "{count, plural, =1{1 öğe} other{{count} öğe}}",
"timeSeconds": "{seconds, plural, =1{1 saniye} other{{seconds} saniye}}",
"timeMinutes": "{minutes, plural, =1{1 dakika} other{{minutes} dakika}}",
"timeDays": "{days, plural, =1{1 gün} other{{days} gün}}",
"focalLength": "{length} mm",
"applyButtonLabel": "UYGULA",
"deleteButtonLabel": "SİL",
"nextButtonLabel": "SONRAKİ",
"showButtonLabel": "GÖSTER",
"hideButtonLabel": "GİZLE",
"continueButtonLabel": "DEVAM ET",
"cancelTooltip": "İptal et",
"changeTooltip": "Değiştir",
"clearTooltip": "Temizle",
"previousTooltip": "Önceki",
"nextTooltip": "Sonraki",
"showTooltip": "Göster",
"hideTooltip": "Gizle",
"actionRemove": "Kaldır",
"resetButtonTooltip": "Sıfırla",
"doubleBackExitMessage": "Çıkmak için tekrar “geri”, düğmesine dokunun.",
"doNotAskAgain": "Bir daha sorma",
"sourceStateLoading": "Yükleniyor",
"sourceStateCataloguing": "Kataloglanıyor",
"sourceStateLocatingCountries": "Ülkeler konumlandırılıyor",
"sourceStateLocatingPlaces": "Konum belirleniyor",
"chipActionDelete": "Sil",
"chipActionGoToAlbumPage": "Albümlerde göster",
"chipActionGoToCountryPage": "Ülkelerde göster",
"chipActionGoToTagPage": "Etiketlerde göster",
"chipActionHide": "Gizle",
"chipActionPin": "Başa sabitle",
"chipActionUnpin": "Baştan çıkar",
"chipActionRename": "Yeniden adlandır",
"chipActionSetCover": "Kapağı ayarla",
"chipActionCreateAlbum": "Albüm oluştur",
"entryActionCopyToClipboard": "Panoya kopyala",
"entryActionDelete": "Sil",
"entryActionConvert": "Dönüştür",
"entryActionExport": "Dışa aktar",
"entryActionInfo": "Bilgi",
"entryActionRename": "Yeniden adlandır",
"entryActionRestore": "Dışa aktar",
"entryActionRotateCCW": "Saat yönünün tersine döndür",
"entryActionRotateCW": "Saat yönünde döndür",
"entryActionFlip": "Yatay olarak çevir",
"entryActionPrint": "Yazdır",
"entryActionShare": "Paylaş",
"entryActionViewSource": "Kaynağı görüntüle",
"entryActionShowGeoTiffOnMap": "Harita katmanı olarak göster",
"entryActionConvertMotionPhotoToStillImage": "Hareketsiz görüntüye dönüştür",
"entryActionViewMotionPhotoVideo": "Videoyu aç",
"entryActionEdit": "Düzenle",
"entryActionOpen": "Şununla aç",
"entryActionSetAs": "Olarak ayarla",
"entryActionOpenMap": "Harita uygulamasında göster",
"entryActionRotateScreen": "Ekranı döndür",
"entryActionAddFavourite": "Favorilere ekle",
"entryActionRemoveFavourite": "Favorilerden kaldır",
"videoActionCaptureFrame": "Çerçeve yakala",
"videoActionMute": "Sustur",
"videoActionUnmute": "Susturmayı kaldır",
"videoActionPause": "Duraklat",
"videoActionPlay": "Oynat",
"videoActionReplay10": "10 saniye geri git",
"videoActionSkip10": "10 saniye ileri git",
"videoActionSelectStreams": "Parça seç",
"videoActionSetSpeed": "Oynatma hızı",
"videoActionSettings": "Ayarlar",
"entryInfoActionEditDate": "Tarih ve saati düzenle",
"entryInfoActionEditLocation": "Konumu düzenle",
"entryInfoActionEditRating": "Derecelendirmeyi düzenle",
"entryInfoActionEditTags": "Etiketleri düzenle",
"entryInfoActionRemoveMetadata": "Meta verileri kaldır",
"filterBinLabel": "Geri dönüşüm kutusu",
"filterFavouriteLabel": "Favori",
"filterLocationEmptyLabel": "Konumsuz",
"filterTagEmptyLabel": "Etiketsiz",
"filterRatingUnratedLabel": "Derecelendirilmemiş",
"filterRatingRejectedLabel": "Reddedilmiş",
"filterTypeAnimatedLabel": "Hareketli",
"filterTypeMotionPhotoLabel": "Hareketli Fotoğraf",
"filterTypePanoramaLabel": "Panorama",
"filterTypeRawLabel": "Raw",
"filterTypeSphericalVideoLabel": "360° Video",
"filterTypeGeotiffLabel": "GeoTIFF",
"filterMimeImageLabel": "Resim",
"filterMimeVideoLabel": "Video",
"coordinateFormatDms": "DMS",
"coordinateFormatDecimal": "Ondalık dereceler",
"coordinateDms": "{coordinate} {direction}",
"coordinateDmsNorth": "K",
"coordinateDmsSouth": "G",
"coordinateDmsEast": "D",
"coordinateDmsWest": "B",
"unitSystemMetric": "Metrik",
"unitSystemImperial": "İngiliz",
"videoLoopModeNever": "Asla",
"videoLoopModeShortOnly": "Yalnızca kısa videolar",
"videoLoopModeAlways": "Her zaman",
"videoControlsPlay": "Oynat",
"videoControlsPlaySeek": "Oynat ve ileri/geri git",
"videoControlsPlayOutside": "Başka bir oynatıcı ile aç",
"videoControlsNone": "Hiçbiri",
"mapStyleGoogleNormal": "Google Haritalar",
"mapStyleGoogleHybrid": "Google Haritalar (Hibrit)",
"mapStyleGoogleTerrain": "Google Haritalar (Arazi)",
"mapStyleHuaweiNormal": "Petal Haritalar",
"mapStyleHuaweiTerrain": "Petal Haritalar (Arazi)",
"mapStyleOsmHot": "İnsancıl OSM",
"mapStyleStamenToner": "Stamen Tonik",
"mapStyleStamenWatercolor": "Stamen Suluboya",
"nameConflictStrategyRename": "Yeniden adlandır",
"nameConflictStrategyReplace": "Değiştir",
"nameConflictStrategySkip": "Atla",
"keepScreenOnNever": "Asla",
"keepScreenOnViewerOnly": "Yalnızca görüntüleyici sayfası",
"keepScreenOnAlways": "Her zaman",
"accessibilityAnimationsRemove": "Ekran efektlerini önle",
"accessibilityAnimationsKeep": "Ekran efektlerini koru",
"displayRefreshRatePreferHighest": "En yüksek oran",
"displayRefreshRatePreferLowest": "En düşük oran",
"themeBrightnessLight": "Açık",
"themeBrightnessDark": "Koyu",
"themeBrightnessBlack": "Siyah",
"albumTierNew": "Yeni",
"albumTierPinned": "Sabitlenmiş",
"albumTierSpecial": "Genel",
"albumTierApps": "Uygulamalar",
"albumTierRegular": "Diğer",
"storageVolumeDescriptionFallbackPrimary": "Dahili depolama",
"storageVolumeDescriptionFallbackNonPrimary": "SD kart",
"rootDirectoryDescription": "kök dizin",
"otherDirectoryDescription": "“{name}” dizin",
"storageAccessDialogTitle": "Depolama Erişimi",
"storageAccessDialogMessage": "Bu uygulamaya erişim sağlamak için lütfen bir sonraki ekranda “{volume}” öğesinin {directory} dizinini seçin.",
"restrictedAccessDialogTitle": "Kısıtlı Erişim",
"restrictedAccessDialogMessage": "Bu uygulamanın “{volume}” içindeki {directory} dosyaları değiştirmesine izin verilmiyor.\n\nÖğeleri başka bir dizine taşımak için lütfen önceden yüklenmiş bir dosya yöneticisi veya galeri uygulaması kullanın.",
"notEnoughSpaceDialogTitle": "Yeterli Yer Yok",
"notEnoughSpaceDialogMessage": "Bu işlemin tamamlanması için “{volume}” üzerinde {needSize} boş alana ihtiyaç var, ancak yalnızca {freeSize} kaldı.",
"missingSystemFilePickerDialogTitle": "Eksik Sistem Dosya Seçicisi",
"missingSystemFilePickerDialogMessage": "Sistem dosya seçicisi eksik veya devre dışı. Lütfen etkinleştirin ve tekrar deneyin.",
"unsupportedTypeDialogTitle": "Desteklenmeyen Türler",
"unsupportedTypeDialogMessage": "{count, plural, =1{Bu işlem aşağıdaki türdeki öğeler için desteklenmez: {types}.} other{Bu işlem aşağıdaki türlerdeki öğeler için desteklenmez: {types}.}}",
"nameConflictDialogSingleSourceMessage": "Hedef klasördeki bazı dosyalar aynı ada sahip.",
"nameConflictDialogMultipleSourceMessage": "Bazı dosyalar aynı ada sahip.",
"addShortcutDialogLabel": "Kısayol etiketi",
"addShortcutButtonLabel": "EKLE",
"noMatchingAppDialogTitle": "Eşleşen Uygulama Yok",
"noMatchingAppDialogMessage": "Bununla ilgilenebilecek bir uygulama yok.",
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öğe geri dönüşüm kutusuna taşınsın mı?} other{Bu {count} madde geri dönüşüm kutusuna atılsın mı?}}",
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öğe silinsin mi?} other{Bu {count} öğe silinsin mi?}}",
"moveUndatedConfirmationDialogMessage": "Devam etmeden önce öğe tarihleri kaydedilsin mi?",
"moveUndatedConfirmationDialogSetDate": "Tarihleri kaydet",
"videoResumeDialogMessage": "{time} itibarıyla oynatmaya devam etmek istiyor musunuz?",
"videoStartOverButtonLabel": "BAŞTAN BAŞLA",
"videoResumeButtonLabel": "SÜRDÜR",
"setCoverDialogLatest": "Son öğe",
"setCoverDialogAuto": "Otomatik",
"setCoverDialogCustom": "Özel",
"hideFilterConfirmationDialogMessage": "Eşleşen fotoğraf ve videolar koleksiyonunuzdan gizlenecektir. Bunları “Gizlilik”, ayarlarından tekrar gösterebilirsiniz.\n\nBunları gizlemek istediğinizden emin misiniz?",
"newAlbumDialogTitle": "Yeni Albüm",
"newAlbumDialogNameLabel": "Albüm adı",
"newAlbumDialogNameLabelAlreadyExistsHelper": "Dizin zaten var",
"newAlbumDialogStorageLabel": "Depolama:",
"renameAlbumDialogLabel": "Yeni ad",
"renameAlbumDialogLabelAlreadyExistsHelper": "Dizin zaten var",
"renameEntrySetPageTitle": "Yeniden adlandır",
"renameEntrySetPagePatternFieldLabel": "İsimlendirme şekli",
"renameEntrySetPageInsertTooltip": "Alan ekle",
"renameEntrySetPagePreview": "Önizleme",
"renameProcessorCounter": "Sayaç",
"renameProcessorName": "Ad",
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albüm ve öğesi silinsin mi?} other{Bu albüm ve {count} öğesi silinsin mi?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albümler ve öğeleri silinsin mi?} other{Bu albümler ve {count} öğesi silinsin mi?}}",
"exportEntryDialogFormat": "Biçim:",
"exportEntryDialogWidth": "Genişlik",
"exportEntryDialogHeight": "Yükseklik",
"renameEntryDialogLabel": "Yeni ad",
"editEntryDateDialogTitle": "Tarih ve Saat",
"editEntryDateDialogSetCustom": "Özel tarih ayarla",
"editEntryDateDialogCopyField": "Başka bir tarihten kopyala",
"editEntryDateDialogCopyItem": "Başka bir öğeden kopyala",
"editEntryDateDialogExtractFromTitle": "Başlıktan ayıkla",
"editEntryDateDialogShift": "Değişim",
"editEntryDateDialogSourceFileModifiedDate": "Dosya değiştirilme tarihi",
"editEntryDateDialogTargetFieldsHeader": "Değiştirilecek alanlar",
"editEntryDateDialogHours": "Saat",
"editEntryDateDialogMinutes": "Dakika",
"editEntryLocationDialogTitle": "Konum",
"editEntryLocationDialogChooseOnMapTooltip": "Harita üzerinde seç",
"editEntryLocationDialogLatitude": "Enlem",
"editEntryLocationDialogLongitude": "Boylam",
"locationPickerUseThisLocationButton": "Bu konumu kullan",
"editEntryRatingDialogTitle": "Derecelendirme",
"removeEntryMetadataDialogTitle": "Meta veri kaldırma",
"removeEntryMetadataDialogMore": "Daha fazla",
"removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "Hareketli bir fotoğrafın içindeki videoyu oynatmak için XMP gereklidir.\n\nKaldırmak istediğinizden emin misiniz?",
"convertMotionPhotoToStillImageWarningDialogMessage": "Emin misiniz?",
"videoSpeedDialogLabel": "Oynatma hızı",
"videoStreamSelectionDialogVideo": "Video",
"videoStreamSelectionDialogAudio": "Ses",
"videoStreamSelectionDialogText": "Altyazı",
"videoStreamSelectionDialogOff": "Kapalı",
"videoStreamSelectionDialogTrack": "Parça",
"videoStreamSelectionDialogNoSelection": "Başka parça yok.",
"genericSuccessFeedback": "Başarılı!",
"genericFailureFeedback": "Başarısız",
"menuActionConfigureView": "Görünüm",
"menuActionSelect": "Seç",
"menuActionSelectAll": "Hepsini seç",
"menuActionSelectNone": "Hiçbirini seçme",
"menuActionMap": "Harita",
"menuActionStats": "İstatistikler",
"viewDialogTabSort": "Sırala",
"viewDialogTabGroup": "Grup",
"viewDialogTabLayout": "Düzen",
"tileLayoutGrid": "Izgara",
"tileLayoutList": "Liste",
"coverDialogTabCover": "Kapak",
"coverDialogTabApp": "Uygulama",
"coverDialogTabColor": "Renk",
"appPickDialogTitle": "Uygulama seç",
"appPickDialogNone": "Yok",
"aboutPageTitle": "Hakkında",
"aboutLinkSources": "Kaynaklar",
"aboutLinkLicense": "Lisans",
"aboutLinkPolicy": "Gizlilik Politikası",
"aboutBug": "Hata Bildirimi",
"aboutBugSaveLogInstruction": "Uygulama günlüklerini bir dosyaya kaydet",
"aboutBugSaveLogButton": "Kaydet",
"aboutBugCopyInfoInstruction": "Sistem bilgilerini kopyala",
"aboutBugCopyInfoButton": "Kopyala",
"aboutBugReportInstruction": "GitHub'da günlükleri ve sistem bilgilerini içeren bir rapor oluştur",
"aboutBugReportButton": "Raporla",
"aboutCredits": "Kredi",
"aboutCreditsWorldAtlas1": "Bu uygulama bir TopoJSON dosyası kullanır",
"aboutCreditsWorldAtlas2": "ISC Lisansı kapsamında.",
"aboutCreditsTranslators": "Tercümanlar",
"aboutLicenses": "Açık Kaynak Lisansları",
"aboutLicensesBanner": "Bu uygulama aşağıdaki açık kaynaklı paketleri ve kütüphaneleri kullanır.",
"aboutLicensesAndroidLibraries": "Android Kütüphaneleri",
"aboutLicensesFlutterPlugins": "Flutter Eklentileri",
"aboutLicensesFlutterPackages": "Flutter Paketleri",
"aboutLicensesDartPackages": "Dart Paketleri",
"aboutLicensesShowAllButtonLabel": "Tüm Lisansları Göster",
"policyPageTitle": "Gizlilik Politikası",
"collectionPageTitle": "Koleksiyon",
"collectionPickPageTitle": "Seç",
"collectionSelectPageTitle": "Öğeleri seç",
"collectionActionShowTitleSearch": "Başlık filtresini göster",
"collectionActionHideTitleSearch": "Başlık filtresini gizle",
"collectionActionAddShortcut": "Kısayol ekle",
"collectionActionEmptyBin": "Boş çöp kutusu",
"collectionActionCopy": "Albüme kopyala",
"collectionActionMove": "Albüme taşı",
"collectionActionRescan": "Yeniden tara",
"collectionActionEdit": "Düzenle",
"collectionSearchTitlesHintText": "Başlıkları ara",
"collectionSortDate": "Tarihe göre",
"collectionSortSize": "Boyuta göre",
"collectionSortName": "Albüm ve dosya adına göre",
"collectionSortRating": "Derecelendirmeye göre",
"collectionGroupAlbum": "Albüme göre",
"collectionGroupMonth": "Aya göre",
"collectionGroupDay": "Güne göre",
"collectionGroupNone": "Gruplama",
"sectionUnknown": "Bilinmeyen",
"dateToday": "Bugün",
"dateYesterday": "Dün",
"dateThisMonth": "Bu ay",
"collectionDeleteFailureFeedback": "{count, plural, =1{1 öğe silinemedi} other{{count} öğe silinemedi}}",
"collectionCopyFailureFeedback": "{count, plural, =1{1 öğe kopyalanamadı} other{{count} öğe kopyalanamadı}}",
"collectionMoveFailureFeedback": "{count, plural, =1{1 öğe taşınamadı} other{{count} öğe taşınamadı}}",
"collectionRenameFailureFeedback": "{count, plural, =1{1 öğenin adı değiştirilemedi} other{{count} öğenin adı değiştirilemedi}}",
"collectionEditFailureFeedback": "{count, plural, =1{1 öğe düzenlenemedi} other{{count} öğe düzenlenemedi}}",
"collectionExportFailureFeedback": "{count, plural, =1{1 sayfa dışa aktarılamadı} other{{count} sayfa dışa aktarılamadı}",
"collectionCopySuccessFeedback": "{count, plural, =1{1 öğe kopyalandı} other{{count} öğe kopyalandı}}",
"collectionMoveSuccessFeedback": "{count, plural, =1{1 öğe taşındı} other{{count} öğe taşındı}}",
"collectionRenameSuccessFeedback": "{count, plural, =1{1 öğenin adı değiştirildi} other{{count} öğenin adı değiştirildi}}",
"collectionEditSuccessFeedback": "{count, plural, =1{1 öğe düzenlendi} other{{count} öğe düzenlendi}}",
"collectionEmptyFavourites": "Favori yok",
"collectionEmptyVideos": "Video yok",
"collectionEmptyImages": "Resim yok",
"collectionEmptyGrantAccessButtonLabel": "Erişim izni",
"collectionSelectSectionTooltip": "Bölüm seç",
"collectionDeselectSectionTooltip": "Bölüm seçimini kaldır",
"drawerCollectionAll": "Tüm koleksiyon",
"drawerCollectionFavourites": "Favoriler",
"drawerCollectionImages": "Resimler",
"drawerCollectionVideos": "Videolar",
"drawerCollectionAnimated": "Hareketli",
"drawerCollectionMotionPhotos": "Hareketli fotoğraflar",
"drawerCollectionPanoramas": "Panoramalar",
"drawerCollectionRaws": "Raw fotoğraflar",
"drawerCollectionSphericalVideos": "360° Videolar",
"chipSortDate": "Tarihe göre",
"chipSortName": "Adına göre",
"chipSortCount": "Öğe sayısına göre",
"albumGroupTier": "Kademeye göre",
"albumGroupVolume": "Depolama hacmine göre",
"albumGroupNone": "Gruplama",
"albumPickPageTitleCopy": "Albüme kopyala",
"albumPickPageTitleExport": "Albüme aktar",
"albumPickPageTitleMove": "Albüme taşı",
"albumPickPageTitlePick": "Albüm seç",
"albumCamera": "Kamera",
"albumDownload": "İndir",
"albumScreenshots": "Ekran görüntüleri",
"albumScreenRecordings": "Ekran kayıtları",
"albumVideoCaptures": "Video çekimleri",
"albumPageTitle": "Albümler",
"albumEmpty": "Albüm yok",
"createAlbumTooltip": "Albüm oluştur",
"createAlbumButtonLabel": "OLUŞTUR",
"newFilterBanner": "yeni",
"countryPageTitle": "Ülkeler",
"countryEmpty": "Ülke yok",
"tagPageTitle": "Etiketler",
"tagEmpty": "Etiket yok",
"binPageTitle": "Geri Dönüşüm Kutusu",
"searchCollectionFieldHint": "Koleksiyonu ara",
"searchSectionRecent": "Yakın zamanda",
"searchSectionAlbums": "Albümler",
"searchSectionCountries": "Ülkeler",
"searchSectionPlaces": "Yerler",
"searchSectionTags": "Etiketler",
"searchSectionRating": "Derecelendirmeler",
"settingsPageTitle": "Ayarlar",
"settingsSystemDefault": "Sistem",
"settingsDefault": "Varsayılan",
"settingsSearchFieldLabel": "Ayarlarda ara",
"settingsSearchEmpty": "Eşleşen ayar bulunamadı",
"settingsActionExport": "Dışa aktar",
"settingsActionImport": "İçe aktar",
"appExportCovers": "Kapaklar",
"appExportFavourites": "Favoriler",
"appExportSettings": "Ayarlar",
"settingsSectionNavigation": "Gezinti",
"settingsHome": "Anasayfa",
"settingsShowBottomNavigationBar": "Alt gezinti çubuğunu göster",
"settingsKeepScreenOnTile": "Ekranıık tut",
"settingsKeepScreenOnTitle": "Ekranıık Tut",
"settingsDoubleBackExit": "Çıkmak için iki kez “geri” düğmesine dokunun",
"settingsConfirmationDialogTile": "Onaylama diyalogları",
"settingsConfirmationDialogTitle": "Onaylama Diyalogları",
"settingsConfirmationDialogDeleteItems": "Öğeleri sonsuza dek silmeden önce sor",
"settingsConfirmationDialogMoveToBinItems": "Eşyaları geri dönüşüm kutusuna atmadan önce sor",
"settingsConfirmationDialogMoveUndatedItems": "Tarihsiz eşyaları taşımadan önce sor",
"settingsNavigationDrawerTile": "Gezinti menüsü",
"settingsNavigationDrawerEditorTitle": "Gezinti Menüsü",
"settingsNavigationDrawerBanner": "Menü öğelerini taşımak ve yeniden sıralamak için dokunun ve basılı tutun.",
"settingsNavigationDrawerTabTypes": "Türler",
"settingsNavigationDrawerTabAlbums": "Albümler",
"settingsNavigationDrawerTabPages": "Sayfalar",
"settingsNavigationDrawerAddAlbum": "Albüm ekle",
"settingsSectionThumbnails": "Küçük resimler",
"settingsThumbnailOverlayTile": "Kaplama",
"settingsThumbnailOverlayTitle": "Kaplama",
"settingsThumbnailShowFavouriteIcon": "Favori simgeyi göster",
"settingsThumbnailShowTagIcon": "Etiket simgesini göster",
"settingsThumbnailShowLocationIcon": "Konum simgesini göster",
"settingsThumbnailShowMotionPhotoIcon": "Hareketli fotoğraf simgesini göster",
"settingsThumbnailShowRating": "Derecelendirmeyi göster",
"settingsThumbnailShowRawIcon": "Raw simgesini göster",
"settingsThumbnailShowVideoDuration": "Video süresini göster",
"settingsCollectionQuickActionsTile": "Hızlı eylemler",
"settingsCollectionQuickActionEditorTitle": "Hızlı Eylemler",
"settingsCollectionQuickActionTabBrowsing": "Gözatma",
"settingsCollectionQuickActionTabSelecting": "Seçme",
"settingsCollectionBrowsingQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğelere göz atarken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.",
"settingsCollectionSelectionQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğeleri seçerken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.",
"settingsSectionViewer": "Görüntüleyici",
"settingsViewerUseCutout": "Kesim alanını kullan",
"settingsViewerMaximumBrightness": "Maksimum parlaklık",
"settingsMotionPhotoAutoPlay": "Hareketli fotoğrafları otomatik oynat",
"settingsImageBackground": "Resim arka planı",
"settingsViewerQuickActionsTile": "Hızlı eylemler",
"settingsViewerQuickActionEditorTitle": "Hızlı Eylemler",
"settingsViewerQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve görüntüleyicide hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.",
"settingsViewerQuickActionEditorDisplayedButtons": "Gösterilen Düğmeler",
"settingsViewerQuickActionEditorAvailableButtons": "Mevcut Düğmeler",
"settingsViewerQuickActionEmpty": "Düğme yok",
"settingsViewerOverlayTile": "Kaplama",
"settingsViewerOverlayTitle": "Kaplama",
"settingsViewerShowOverlayOnOpening": "Açılışta göster",
"settingsViewerShowMinimap": "Mini haritayı göster",
"settingsViewerShowInformation": "Bilgileri göster",
"settingsViewerShowInformationSubtitle": "Başlığı, tarihi, konumu vb. göster.",
"settingsViewerShowShootingDetails": "Çekim ayrıntılarını göster",
"settingsViewerShowOverlayThumbnails": "Küçük resimleri göster",
"settingsViewerEnableOverlayBlurEffect": "Bulanıklık efekti",
"settingsVideoPageTitle": "Video Ayarları",
"settingsSectionVideo": "Video",
"settingsVideoShowVideos": "Videoları göster",
"settingsVideoEnableHardwareAcceleration": "Donanım hızlandırma",
"settingsVideoEnableAutoPlay": "Otomatik oynat",
"settingsVideoLoopModeTile": "Döngü modu",
"settingsVideoLoopModeTitle": "Döngü Modu",
"settingsSubtitleThemeTile": "Altyazılar",
"settingsSubtitleThemeTitle": "Altyazılar",
"settingsSubtitleThemeSample": "Bu bir örnek.",
"settingsSubtitleThemeTextAlignmentTile": "Metin hizalama",
"settingsSubtitleThemeTextAlignmentTitle": "Metin Hizalama",
"settingsSubtitleThemeTextSize": "Metin boyutu",
"settingsSubtitleThemeShowOutline": "Dış çizgiyi ve gölgeyi göster",
"settingsSubtitleThemeTextColor": "Metin rengi",
"settingsSubtitleThemeTextOpacity": "Metin opaklığı",
"settingsSubtitleThemeBackgroundColor": "Arka plan rengi",
"settingsSubtitleThemeBackgroundOpacity": "Arka plan opaklığı",
"settingsSubtitleThemeTextAlignmentLeft": "Sol",
"settingsSubtitleThemeTextAlignmentCenter": "Merkez",
"settingsSubtitleThemeTextAlignmentRight": "Sağ",
"settingsVideoControlsTile": "Kontroller",
"settingsVideoControlsTitle": "Kontroller",
"settingsVideoButtonsTile": "Düğmeler",
"settingsVideoButtonsTitle": "Düğmeler",
"settingsVideoGestureDoubleTapTogglePlay": "Oynatmak/duraklatmak için çift dokunun",
"settingsVideoGestureSideDoubleTapSeek": "Geri/ileri aramak için ekran kenarlarına çift dokunun",
"settingsSectionPrivacy": "Gizlilik",
"settingsAllowInstalledAppAccess": "Uygulama envanterine erişime izin ver",
"settingsAllowInstalledAppAccessSubtitle": "Albüm görüntüsünü iyileştirmek için kullanılır",
"settingsAllowErrorReporting": "Anonim hata raporlamasına izin ver",
"settingsSaveSearchHistory": "Arama geçmişini kaydet",
"settingsEnableBin": "Geri dönüşüm kutusunu kullan",
"settingsEnableBinSubtitle": "Silinen öğeleri 30 gün boyunca saklar",
"settingsHiddenItemsTile": "Gizli öğeler",
"settingsHiddenItemsTitle": "Gizli Öğeler",
"settingsHiddenFiltersTitle": "Gizli Filtreler",
"settingsHiddenFiltersBanner": "Gizli filtrelerle eşleşen fotoğraflar ve videolar koleksiyonunuzda görünmeyecektir.",
"settingsHiddenFiltersEmpty": "Gizli filtre yok",
"settingsHiddenPathsTitle": "Gizli Yollar",
"settingsHiddenPathsBanner": "Bu klasörlerdeki veya alt klasörlerindeki fotoğraflar ve videolar koleksiyonunuzda görünmeyecektir.",
"addPathTooltip": "Yol ekle",
"settingsStorageAccessTile": "Depolama erişimi",
"settingsStorageAccessTitle": "Depolama Erişimi",
"settingsStorageAccessBanner": "Bazı dizinler, içlerindeki dosyaları değiştirmek için açık bir erişim izni gerektirir. Daha önce erişim izni verdiğiniz dizinleri buradan inceleyebilirsiniz.",
"settingsStorageAccessEmpty": "Erişim izni yok",
"settingsStorageAccessRevokeTooltip": "Geri al",
"settingsSectionAccessibility": "Erişilebilirlik",
"settingsRemoveAnimationsTile": "Animasyonları kaldır",
"settingsRemoveAnimationsTitle": "Animasyonları Kaldır",
"settingsTimeToTakeActionTile": "Harekete geçme zamanı",
"settingsTimeToTakeActionTitle": "Harekete Geçme Zamanı",
"settingsSectionDisplay": "Ekran",
"settingsThemeBrightness": "Tema",
"settingsThemeColorHighlights": "Renk vurguları",
"settingsThemeEnableDynamicColor": "Dinamik renk",
"settingsDisplayRefreshRateModeTile": "Görüntü yenileme hızı",
"settingsDisplayRefreshRateModeTitle": "Yenileme Hızı",
"settingsSectionLanguage": "Dil ve Biçimler",
"settingsLanguage": "Dil",
"settingsCoordinateFormatTile": "Koordinat formatı",
"settingsCoordinateFormatTitle": "Koordinat Formatı",
"settingsUnitSystemTile": "Birimler",
"settingsUnitSystemTitle": "Birimler",
"statsPageTitle": "İstatistikler",
"statsWithGps": "{count, plural, =1{1 konuma sahip öğe} other{{count} konuma sahip öğe}}",
"statsTopCountries": "Başlıca Ülkeler",
"statsTopPlaces": "Başlıca Yerler",
"statsTopTags": "Başlıca Etiketler",
"viewerOpenPanoramaButtonLabel": "PANORAMAYI AÇ",
"viewerErrorUnknown": "Tüh!",
"viewerErrorDoesNotExist": "Dosya artık mevcut değil.",
"viewerInfoPageTitle": "Bilgi",
"viewerInfoBackToViewerTooltip": "Görüntüleyiciye geri dön",
"viewerInfoUnknown": "bilinmeyen",
"viewerInfoLabelTitle": "Başlık",
"viewerInfoLabelDate": "Tarih",
"viewerInfoLabelResolution": "Çözünürlük",
"viewerInfoLabelSize": "Boyut",
"viewerInfoLabelUri": "URI",
"viewerInfoLabelPath": "Yol",
"viewerInfoLabelDuration": "Süre",
"viewerInfoLabelOwner": "Sahibi",
"viewerInfoLabelCoordinates": "Koordinatlar",
"viewerInfoLabelAddress": "Adres",
"mapStyleTitle": "Harita Şekli",
"mapStyleTooltip": "Harita şeklini seç",
"mapZoomInTooltip": "Yakınlaştır",
"mapZoomOutTooltip": "Uzaklaştır",
"mapPointNorthUpTooltip": "Kuzeyi göster",
"mapAttributionOsmHot": "Harita verileri © [OpenStreetMap](https://www.openstreetmap.org/copyright) katkıda bulunanlar - Kutucuklar [HOT](https://www.hotosm.org/) tarafından hazırlanmıştır - [OSM France](https://openstreetmap.fr/) tarafından barındırılmaktadır",
"mapAttributionStamen": "Harita verileri © [OpenStreetMap](https://www.openstreetmap.org/copyright) katkıda bulunanlar - Döşemeler [Stamen Design](http://stamen.com), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0)",
"openMapPageTooltip": "Harita sayfasında görüntüle",
"mapEmptyRegion": "Bu bölgede resim yok",
"viewerInfoOpenEmbeddedFailureFeedback": "Gömülü veriler ayıklanamadı",
"viewerInfoOpenLinkText": "Aç",
"viewerInfoViewXmlLinkText": "XML'i Görüntüle",
"viewerInfoSearchFieldLabel": "Meta verileri ara",
"viewerInfoSearchEmpty": "Eşleşen anahtar yok",
"viewerInfoSearchSuggestionDate": "Tarih ve saat",
"viewerInfoSearchSuggestionDescription": "Açıklama",
"viewerInfoSearchSuggestionDimensions": "Boyutlar",
"viewerInfoSearchSuggestionResolution": "Çözünürlük",
"viewerInfoSearchSuggestionRights": "Haklar",
"tagEditorPageTitle": "Etiketleri Düzenle",
"tagEditorPageNewTagFieldLabel": "Yeni etiket",
"tagEditorPageAddTagTooltip": "Etiket ekle",
"tagEditorSectionRecent": "Yakın zamanda",
"panoramaEnableSensorControl": "Sensör kontrolünü etkinleştir",
"panoramaDisableSensorControl": "Sensör kontrolünü devre dışı bırak",
"sourceViewerPageTitle": "Kaynak",
"filePickerShowHiddenFiles": "Gizli dosyaları göster",
"filePickerDoNotShowHiddenFiles": "Gizli dosyaları gösterme",
"filePickerOpenFrom": "Şuradan aç",
"filePickerNoItems": "Öğe yok",
"filePickerUseThisFolder": "Bu klasörü kullan"
}

View file

@ -50,6 +50,7 @@
"entryActionDelete": "删除", "entryActionDelete": "删除",
"entryActionConvert": "转换", "entryActionConvert": "转换",
"entryActionExport": "导出", "entryActionExport": "导出",
"entryActionInfo": "信息",
"entryActionRename": "重命名", "entryActionRename": "重命名",
"entryActionRestore": "恢复", "entryActionRestore": "恢复",
"entryActionRotateCCW": "逆时针旋转", "entryActionRotateCCW": "逆时针旋转",
@ -80,6 +81,9 @@
"videoActionSetSpeed": "播放速度", "videoActionSetSpeed": "播放速度",
"videoActionSettings": "设置", "videoActionSettings": "设置",
"slideshowActionResume": "继续",
"slideshowActionShowInCollection": "在媒体集中显示",
"entryInfoActionEditDate": "编辑日期和时间", "entryInfoActionEditDate": "编辑日期和时间",
"entryInfoActionEditLocation": "编辑位置", "entryInfoActionEditLocation": "编辑位置",
"entryInfoActionEditRating": "修改评分", "entryInfoActionEditRating": "修改评分",
@ -144,10 +148,23 @@
"displayRefreshRatePreferHighest": "最高刷新率", "displayRefreshRatePreferHighest": "最高刷新率",
"displayRefreshRatePreferLowest": "最低刷新率", "displayRefreshRatePreferLowest": "最低刷新率",
"slideshowVideoPlaybackSkip": "跳过",
"slideshowVideoPlaybackMuted": "静音播放",
"slideshowVideoPlaybackWithSound": "带音播放",
"themeBrightnessLight": "浅色", "themeBrightnessLight": "浅色",
"themeBrightnessDark": "深色", "themeBrightnessDark": "深色",
"themeBrightnessBlack": "黑色", "themeBrightnessBlack": "黑色",
"viewerTransitionSlide": "滑动",
"viewerTransitionParallax": "视差滚动",
"viewerTransitionFade": "淡入淡出",
"viewerTransitionZoomIn": "放大",
"wallpaperTargetHome": "主屏幕",
"wallpaperTargetLock": "锁屏界面",
"wallpaperTargetHomeLock": "主屏幕 + 锁屏界面",
"albumTierNew": "新的", "albumTierNew": "新的",
"albumTierPinned": "钉选", "albumTierPinned": "钉选",
"albumTierSpecial": "普通", "albumTierSpecial": "普通",
@ -262,6 +279,7 @@
"menuActionSelectAll": "全选", "menuActionSelectAll": "全选",
"menuActionSelectNone": "全不选", "menuActionSelectNone": "全不选",
"menuActionMap": "地图", "menuActionMap": "地图",
"menuActionSlideshow": "幻灯片",
"menuActionStats": "统计", "menuActionStats": "统计",
"viewDialogTabSort": "排序", "viewDialogTabSort": "排序",
@ -349,6 +367,7 @@
"collectionEmptyFavourites": "无收藏项", "collectionEmptyFavourites": "无收藏项",
"collectionEmptyVideos": "无视频", "collectionEmptyVideos": "无视频",
"collectionEmptyImages": "无图像", "collectionEmptyImages": "无图像",
"collectionEmptyGrantAccessButtonLabel": "授予访问权限",
"collectionSelectSectionTooltip": "选择部分", "collectionSelectSectionTooltip": "选择部分",
"collectionDeselectSectionTooltip": "取消选择部分", "collectionDeselectSectionTooltip": "取消选择部分",
@ -479,6 +498,17 @@
"settingsViewerShowOverlayThumbnails": "显示缩略图", "settingsViewerShowOverlayThumbnails": "显示缩略图",
"settingsViewerEnableOverlayBlurEffect": "模糊特效", "settingsViewerEnableOverlayBlurEffect": "模糊特效",
"settingsViewerSlideshowTile": "幻灯片",
"settingsViewerSlideshowTitle": "幻灯片",
"settingsSlideshowRepeat": "重复",
"settingsSlideshowShuffle": "随机播放",
"settingsSlideshowTransitionTile": "过渡动画",
"settingsSlideshowTransitionTitle": "过渡动画",
"settingsSlideshowIntervalTile": "时间间隔",
"settingsSlideshowIntervalTitle": "时间间隔",
"settingsSlideshowVideoPlaybackTile": "视频回放",
"settingsSlideshowVideoPlaybackTitle": "视频回放",
"settingsVideoPageTitle": "视频设置", "settingsVideoPageTitle": "视频设置",
"settingsSectionVideo": "视频", "settingsSectionVideo": "视频",
"settingsVideoShowVideos": "显示视频", "settingsVideoShowVideos": "显示视频",
@ -543,6 +573,7 @@
"settingsSectionDisplay": "显示", "settingsSectionDisplay": "显示",
"settingsThemeBrightness": "主题", "settingsThemeBrightness": "主题",
"settingsThemeColorHighlights": "色彩强调", "settingsThemeColorHighlights": "色彩强调",
"settingsThemeEnableDynamicColor": "动态色彩",
"settingsDisplayRefreshRateModeTile": "显示刷新率", "settingsDisplayRefreshRateModeTile": "显示刷新率",
"settingsDisplayRefreshRateModeTitle": "刷新率", "settingsDisplayRefreshRateModeTitle": "刷新率",
@ -560,6 +591,7 @@
"statsTopTags": "热门标签", "statsTopTags": "热门标签",
"viewerOpenPanoramaButtonLabel": "打开全景", "viewerOpenPanoramaButtonLabel": "打开全景",
"viewerSetWallpaperButtonLabel": "设置壁纸",
"viewerErrorUnknown": "糟糕!", "viewerErrorUnknown": "糟糕!",
"viewerErrorDoesNotExist": "该文件不存在", "viewerErrorDoesNotExist": "该文件不存在",

View file

@ -13,6 +13,7 @@ enum ChipSetAction {
createAlbum, createAlbum,
// browsing or selecting // browsing or selecting
map, map,
slideshow,
stats, stats,
// selecting (single/multiple filters) // selecting (single/multiple filters)
delete, delete,
@ -36,6 +37,7 @@ class ChipSetActions {
ChipSetAction.search, ChipSetAction.search,
ChipSetAction.createAlbum, ChipSetAction.createAlbum,
ChipSetAction.map, ChipSetAction.map,
ChipSetAction.slideshow,
ChipSetAction.stats, ChipSetAction.stats,
]; ];
@ -47,6 +49,7 @@ class ChipSetActions {
ChipSetAction.rename, ChipSetAction.rename,
ChipSetAction.hide, ChipSetAction.hide,
ChipSetAction.map, ChipSetAction.map,
ChipSetAction.slideshow,
ChipSetAction.stats, ChipSetAction.stats,
]; ];
} }
@ -71,6 +74,8 @@ extension ExtraChipSetAction on ChipSetAction {
// browsing or selecting // browsing or selecting
case ChipSetAction.map: case ChipSetAction.map:
return context.l10n.menuActionMap; return context.l10n.menuActionMap;
case ChipSetAction.slideshow:
return context.l10n.menuActionSlideshow;
case ChipSetAction.stats: case ChipSetAction.stats:
return context.l10n.menuActionStats; return context.l10n.menuActionStats;
// selecting (single/multiple filters) // selecting (single/multiple filters)
@ -111,6 +116,8 @@ extension ExtraChipSetAction on ChipSetAction {
// browsing or selecting // browsing or selecting
case ChipSetAction.map: case ChipSetAction.map:
return AIcons.map; return AIcons.map;
case ChipSetAction.slideshow:
return AIcons.slideshow;
case ChipSetAction.stats: case ChipSetAction.stats:
return AIcons.stats; return AIcons.stats;
// selecting (single/multiple filters) // selecting (single/multiple filters)

View file

@ -4,6 +4,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
enum EntryAction { enum EntryAction {
info,
addShortcut, addShortcut,
copyToClipboard, copyToClipboard,
delete, delete,
@ -43,6 +44,7 @@ enum EntryAction {
class EntryActions { class EntryActions {
static const topLevel = [ static const topLevel = [
EntryAction.info,
EntryAction.share, EntryAction.share,
EntryAction.edit, EntryAction.edit,
EntryAction.rename, EntryAction.rename,
@ -102,6 +104,8 @@ class EntryActions {
extension ExtraEntryAction on EntryAction { extension ExtraEntryAction on EntryAction {
String getText(BuildContext context) { String getText(BuildContext context) {
switch (this) { switch (this) {
case EntryAction.info:
return context.l10n.entryActionInfo;
case EntryAction.addShortcut: case EntryAction.addShortcut:
return context.l10n.collectionActionAddShortcut; return context.l10n.collectionActionAddShortcut;
case EntryAction.copyToClipboard: case EntryAction.copyToClipboard:
@ -188,6 +192,8 @@ extension ExtraEntryAction on EntryAction {
IconData getIconData() { IconData getIconData() {
switch (this) { switch (this) {
case EntryAction.info:
return AIcons.info;
case EntryAction.addShortcut: case EntryAction.addShortcut:
return AIcons.addShortcut; return AIcons.addShortcut;
case EntryAction.copyToClipboard: case EntryAction.copyToClipboard:

View file

@ -15,6 +15,7 @@ enum EntrySetAction {
emptyBin, emptyBin,
// browsing or selecting // browsing or selecting
map, map,
slideshow,
stats, stats,
rescan, rescan,
// selecting // selecting
@ -48,6 +49,7 @@ class EntrySetActions {
EntrySetAction.toggleTitleSearch, EntrySetAction.toggleTitleSearch,
EntrySetAction.addShortcut, EntrySetAction.addShortcut,
EntrySetAction.map, EntrySetAction.map,
EntrySetAction.slideshow,
EntrySetAction.stats, EntrySetAction.stats,
EntrySetAction.rescan, EntrySetAction.rescan,
EntrySetAction.emptyBin, EntrySetAction.emptyBin,
@ -59,6 +61,7 @@ class EntrySetActions {
EntrySetAction.toggleTitleSearch, EntrySetAction.toggleTitleSearch,
EntrySetAction.addShortcut, EntrySetAction.addShortcut,
EntrySetAction.map, EntrySetAction.map,
EntrySetAction.slideshow,
EntrySetAction.stats, EntrySetAction.stats,
EntrySetAction.rescan, EntrySetAction.rescan,
]; ];
@ -72,6 +75,7 @@ class EntrySetActions {
EntrySetAction.rename, EntrySetAction.rename,
EntrySetAction.toggleFavourite, EntrySetAction.toggleFavourite,
EntrySetAction.map, EntrySetAction.map,
EntrySetAction.slideshow,
EntrySetAction.stats, EntrySetAction.stats,
EntrySetAction.rescan, EntrySetAction.rescan,
// editing actions are in their subsection // editing actions are in their subsection
@ -86,6 +90,7 @@ class EntrySetActions {
EntrySetAction.rename, EntrySetAction.rename,
EntrySetAction.toggleFavourite, EntrySetAction.toggleFavourite,
EntrySetAction.map, EntrySetAction.map,
EntrySetAction.slideshow,
EntrySetAction.stats, EntrySetAction.stats,
EntrySetAction.rescan, EntrySetAction.rescan,
// editing actions are in their subsection // editing actions are in their subsection
@ -125,6 +130,8 @@ extension ExtraEntrySetAction on EntrySetAction {
// browsing or selecting // browsing or selecting
case EntrySetAction.map: case EntrySetAction.map:
return context.l10n.menuActionMap; return context.l10n.menuActionMap;
case EntrySetAction.slideshow:
return context.l10n.menuActionSlideshow;
case EntrySetAction.stats: case EntrySetAction.stats:
return context.l10n.menuActionStats; return context.l10n.menuActionStats;
case EntrySetAction.rescan: case EntrySetAction.rescan:
@ -190,6 +197,8 @@ extension ExtraEntrySetAction on EntrySetAction {
// browsing or selecting // browsing or selecting
case EntrySetAction.map: case EntrySetAction.map:
return AIcons.map; return AIcons.map;
case EntrySetAction.slideshow:
return AIcons.slideshow;
case EntrySetAction.stats: case EntrySetAction.stats:
return AIcons.stats; return AIcons.stats;
case EntrySetAction.rescan: case EntrySetAction.rescan:

View file

@ -0,0 +1,30 @@
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/material.dart';
enum SlideshowAction {
resume,
showInCollection,
}
extension ExtraSlideshowAction on SlideshowAction {
String getText(BuildContext context) {
switch (this) {
case SlideshowAction.resume:
return context.l10n.slideshowActionResume;
case SlideshowAction.showInCollection:
return context.l10n.slideshowActionShowInCollection;
}
}
Widget getIcon() => Icon(_getIconData());
IconData _getIconData() {
switch (this) {
case SlideshowAction.resume:
return AIcons.play;
case SlideshowAction.showInCollection:
return AIcons.allCollection;
}
}
}

View file

@ -5,8 +5,8 @@ final Device device = Device._private();
class Device { class Device {
late final String _userAgent; late final String _userAgent;
late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis; late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis, _canSetLockScreenWallpaper;
late final bool _showPinShortcutFeedback, _supportEdgeToEdgeUIMode; late final bool _isDynamicColorAvailable, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode;
String get userAgent => _userAgent; String get userAgent => _userAgent;
@ -18,6 +18,10 @@ class Device {
bool get canRenderFlagEmojis => _canRenderFlagEmojis; bool get canRenderFlagEmojis => _canRenderFlagEmojis;
bool get canSetLockScreenWallpaper => _canSetLockScreenWallpaper;
bool get isDynamicColorAvailable => _isDynamicColorAvailable;
bool get showPinShortcutFeedback => _showPinShortcutFeedback; bool get showPinShortcutFeedback => _showPinShortcutFeedback;
bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode; bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode;
@ -33,6 +37,8 @@ class Device {
_canPinShortcut = capabilities['canPinShortcut'] ?? false; _canPinShortcut = capabilities['canPinShortcut'] ?? false;
_canPrint = capabilities['canPrint'] ?? false; _canPrint = capabilities['canPrint'] ?? false;
_canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false; _canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false;
_canSetLockScreenWallpaper = capabilities['canSetLockScreenWallpaper'] ?? false;
_isDynamicColorAvailable = capabilities['isDynamicColorAvailable'] ?? false;
_showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false; _showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false;
_supportEdgeToEdgeUIMode = capabilities['supportEdgeToEdgeUIMode'] ?? false; _supportEdgeToEdgeUIMode = capabilities['supportEdgeToEdgeUIMode'] ?? false;
} }

View file

@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:aves/geo/countries.dart'; import 'package:aves/geo/countries.dart';
import 'package:aves/model/entry_cache.dart'; import 'package:aves/model/entry_cache.dart';
import 'package:aves/model/entry_dirs.dart';
import 'package:aves/model/favourites.dart'; import 'package:aves/model/favourites.dart';
import 'package:aves/model/geotiff.dart'; import 'package:aves/model/geotiff.dart';
import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/address.dart';
@ -31,7 +32,8 @@ class AvesEntry {
// `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode // `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode
int id; int id;
String uri; String uri;
String? _path, _directory, _filename, _extension, _sourceTitle; String? _path, _filename, _extension, _sourceTitle;
EntryDir? _directory;
int? pageId, contentId; int? pageId, contentId;
final String sourceMimeType; final String sourceMimeType;
int width, height, sourceRotationDegrees; int width, height, sourceRotationDegrees;
@ -175,8 +177,8 @@ class AvesEntry {
// directory path, without the trailing separator // directory path, without the trailing separator
String? get directory { String? get directory {
_directory ??= path != null ? pContext.dirname(path!) : null; _directory ??= entryDirRepo.getOrCreate(path != null ? pContext.dirname(path!) : null);
return _directory; return _directory!.resolved;
} }
String? get filenameWithoutExtension { String? get filenameWithoutExtension {

68
lib/model/entry_dirs.dart Normal file
View file

@ -0,0 +1,68 @@
import 'dart:async';
import 'dart:io';
import 'package:aves/services/common/services.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:collection/collection.dart';
final entryDirRepo = EntryDirRepo._private();
class EntryDirRepo {
EntryDirRepo._private();
// mapping between the raw entry directory path to a resolvable directory
final Map<String?, EntryDir> _dirs = {};
final StreamController<EntryDir> _ambiguousDirStreamController = StreamController.broadcast();
Stream<EntryDir> get ambiguousDirStream => _ambiguousDirStreamController.stream;
// get a resolvable directory for a raw entry directory path
EntryDir getOrCreate(String? asIs) {
var entryDir = _dirs[asIs];
if (entryDir != null) return entryDir;
final asIsLower = asIs?.toLowerCase();
entryDir = _dirs.values.firstWhereOrNull((dir) => dir.asIsLower == asIsLower);
if (entryDir != null && !entryDir.ambiguous) {
entryDir.ambiguous = true;
_ambiguousDirStreamController.add(entryDir);
}
return _dirs.putIfAbsent(asIs, () => entryDir ?? EntryDir(asIs));
}
}
// Some directories are ambiguous because they use different cases,
// but the OS merge and present them as one directory.
// This class resolves ambiguous directories to get the directory path
// with the right case, as presented by the OS.
class EntryDir {
final String? asIs, asIsLower;
bool ambiguous = false;
String? _resolved;
EntryDir(this.asIs) : asIsLower = asIs?.toLowerCase();
String? get resolved {
if (!ambiguous) return asIs;
if (asIs == null) return null;
_resolved ??= _resolve();
return _resolved;
}
String? _resolve() {
final vrl = VolumeRelativeDirectory.fromPath(asIs!);
if (vrl == null || vrl.relativeDir.isEmpty) return asIs;
var resolved = vrl.volumePath;
final parts = pContext.split(vrl.relativeDir);
for (final part in parts) {
final partLower = part.toLowerCase();
final childrenDirs = Directory(resolved).listSync().where((v) => v.absolute is Directory).toSet();
final found = childrenDirs.firstWhereOrNull((v) => pContext.basename(v.path).toLowerCase() == partLower);
resolved = found?.path ?? '$resolved${pContext.separator}$part';
}
return resolved;
}
}

View file

@ -5,6 +5,7 @@ import 'package:aves/image_providers/thumbnail_provider.dart';
import 'package:aves/image_providers/uri_image_provider.dart'; import 'package:aves/image_providers/uri_image_provider.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_cache.dart'; import 'package:aves/model/entry_cache.dart';
import 'package:aves/utils/math_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -63,4 +64,15 @@ extension ExtraAvesEntryImages on AvesEntry {
final sizedThumbnailKey = EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).firstWhereOrNull(_isReady); final sizedThumbnailKey = EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).firstWhereOrNull(_isReady);
return sizedThumbnailKey != null ? ThumbnailProvider(sizedThumbnailKey) : getThumbnail(); return sizedThumbnailKey != null ? ThumbnailProvider(sizedThumbnailKey) : getThumbnail();
} }
// magic number used to derive sample size from scale
static const scaleFactor = 2.0;
static int sampleSizeForScale(double scale) {
var sample = 0;
if (0 < scale && scale < 1) {
sample = highestPowerOf2((1 / scale) / scaleFactor);
}
return max<int>(1, sample);
}
} }

View file

@ -2,6 +2,7 @@ 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';
import 'package:aves/utils/file_utils.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -19,11 +20,25 @@ class QueryFilter extends CollectionFilter {
@override @override
List<Object?> get props => [query, live]; List<Object?> get props => [query, live];
static final _fieldPattern = RegExp(r'(.+)([=<>])(.+)');
static final _fileSizePattern = RegExp(r'(\d+)([KMG])?');
static const keyContentId = 'ID';
static const keyContentYear = 'YEAR';
static const keyContentMonth = 'MONTH';
static const keyContentDay = 'DAY';
static const keyContentWidth = 'WIDTH';
static const keyContentHeight = 'HEIGHT';
static const keyContentSize = 'SIZE';
static const opEqual = '=';
static const opLower = '<';
static const opGreater = '>';
QueryFilter(this.query, {this.colorful = true, this.live = false}) { QueryFilter(this.query, {this.colorful = true, this.live = false}) {
var upQuery = query.toUpperCase(); var upQuery = query.toUpperCase();
if (upQuery.startsWith('ID:')) {
final id = int.tryParse(upQuery.substring(3)); final test = fieldTest(upQuery);
_test = (entry) => entry.id == id; if (test != null) {
_test = test;
return; return;
} }
@ -82,4 +97,114 @@ class QueryFilter extends CollectionFilter {
@override @override
String get key => '$type-$query'; String get key => '$type-$query';
EntryFilter? fieldTest(String upQuery) {
var match = _fieldPattern.firstMatch(upQuery);
if (match == null) return null;
final key = match.group(1)?.trim();
final op = match.group(2)?.trim();
var valueString = match.group(3)?.trim();
if (key == null || op == null || valueString == null) return null;
final valueInt = int.tryParse(valueString);
switch (key) {
case keyContentId:
if (valueInt == null) return null;
if (op == opEqual) {
return (entry) => entry.contentId == valueInt;
}
break;
case keyContentYear:
if (valueInt == null) return null;
switch (op) {
case opEqual:
return (entry) => (entry.bestDate?.year ?? 0) == valueInt;
case opLower:
return (entry) => (entry.bestDate?.year ?? 0) < valueInt;
case opGreater:
return (entry) => (entry.bestDate?.year ?? 0) > valueInt;
}
break;
case keyContentMonth:
if (valueInt == null) return null;
switch (op) {
case opEqual:
return (entry) => (entry.bestDate?.month ?? 0) == valueInt;
case opLower:
return (entry) => (entry.bestDate?.month ?? 0) < valueInt;
case opGreater:
return (entry) => (entry.bestDate?.month ?? 0) > valueInt;
}
break;
case keyContentDay:
if (valueInt == null) return null;
switch (op) {
case opEqual:
return (entry) => (entry.bestDate?.day ?? 0) == valueInt;
case opLower:
return (entry) => (entry.bestDate?.day ?? 0) < valueInt;
case opGreater:
return (entry) => (entry.bestDate?.day ?? 0) > valueInt;
}
break;
case keyContentWidth:
if (valueInt == null) return null;
switch (op) {
case opEqual:
return (entry) => entry.displaySize.width == valueInt;
case opLower:
return (entry) => entry.displaySize.width < valueInt;
case opGreater:
return (entry) => entry.displaySize.width > valueInt;
}
break;
case keyContentHeight:
if (valueInt == null) return null;
switch (op) {
case opEqual:
return (entry) => entry.displaySize.height == valueInt;
case opLower:
return (entry) => entry.displaySize.height < valueInt;
case opGreater:
return (entry) => entry.displaySize.height > valueInt;
}
break;
case keyContentSize:
match = _fileSizePattern.firstMatch(valueString);
if (match == null) return null;
valueString = match.group(1)?.trim();
if (valueString == null) return null;
final valueInt = int.tryParse(valueString);
if (valueInt == null) return null;
var bytes = valueInt;
final multiplierString = match.group(2)?.trim();
switch (multiplierString) {
case 'K':
bytes *= kilo;
break;
case 'M':
bytes *= mega;
break;
case 'G':
bytes *= giga;
break;
}
switch (op) {
case opEqual:
return (entry) => (entry.sizeBytes ?? 0) == bytes;
case opLower:
return (entry) => (entry.sizeBytes ?? 0) < bytes;
case opGreater:
return (entry) => (entry.sizeBytes ?? 0) > bytes;
}
break;
}
return null;
}
} }

View file

@ -17,11 +17,15 @@ class SettingsDefaults {
static const canUseAnalysisService = true; static const canUseAnalysisService = true;
static const isInstalledAppAccessAllowed = false; static const isInstalledAppAccessAllowed = false;
static const isErrorReportingAllowed = false; static const isErrorReportingAllowed = false;
static const tileLayout = TileLayout.grid;
static const entryRenamingPattern = '<${DateNamingProcessor.key}, yyyyMMdd-HHmmss> <${NameNamingProcessor.key}>';
// display
static const displayRefreshRateMode = DisplayRefreshRateMode.auto; static const displayRefreshRateMode = DisplayRefreshRateMode.auto;
static const themeBrightness = AvesThemeBrightness.system; static const themeBrightness = AvesThemeBrightness.system;
static const themeColorMode = AvesThemeColorMode.polychrome; static const themeColorMode = AvesThemeColorMode.polychrome;
static const tileLayout = TileLayout.grid; static const enableDynamicColor = false;
static const entryRenamingPattern = '<${DateNamingProcessor.key}, yyyyMMdd-HHmmss> <${NameNamingProcessor.key}>'; static const enableBlurEffect = true; // `enableBlurEffect` has a contextual default value
// navigation // navigation
static const mustBackTwiceToExit = true; static const mustBackTwiceToExit = true;
@ -79,7 +83,6 @@ class SettingsDefaults {
static const showOverlayInfo = true; static const showOverlayInfo = true;
static const showOverlayShootingDetails = false; static const showOverlayShootingDetails = false;
static const showOverlayThumbnailPreview = false; static const showOverlayThumbnailPreview = false;
static const enableOverlayBlurEffect = true; // `enableOverlayBlurEffect` has a contextual default value
static const viewerUseCutout = true; static const viewerUseCutout = true;
static const viewerMaxBrightness = false; static const viewerMaxBrightness = false;
static const enableMotionPhotoAutoPlay = false; static const enableMotionPhotoAutoPlay = false;
@ -122,6 +125,13 @@ class SettingsDefaults {
// file picker // file picker
static const filePickerShowHiddenFiles = false; static const filePickerShowHiddenFiles = false;
// slideshow
static const slideshowRepeat = false;
static const slideshowShuffle = false;
static const slideshowTransition = ViewerTransition.fade;
static const slideshowVideoPlayback = SlideshowVideoPlayback.playMuted;
static const slideshowInterval = SlideshowInterval.s5;
// platform settings // platform settings
static const isRotationLocked = false; static const isRotationLocked = false;
static const areAnimationsRemoved = false; static const areAnimationsRemoved = false;

View file

@ -2,24 +2,30 @@ enum AccessibilityAnimations { system, disabled, enabled }
enum AccessibilityTimeout { system, appDefault, s3, s10, s30, s60, s120 } enum AccessibilityTimeout { system, appDefault, s3, s10, s30, s60, s120 }
enum AvesThemeColorMode { monochrome, polychrome }
enum AvesThemeBrightness { system, light, dark, black } enum AvesThemeBrightness { system, light, dark, black }
enum AvesThemeColorMode { monochrome, polychrome }
enum ConfirmationDialog { deleteForever, moveToBin, moveUndatedItems } enum ConfirmationDialog { deleteForever, moveToBin, moveUndatedItems }
enum CoordinateFormat { dms, decimal } enum CoordinateFormat { dms, decimal }
enum DisplayRefreshRateMode { auto, highest, lowest }
enum EntryBackground { black, white, checkered } enum EntryBackground { black, white, checkered }
enum HomePageSetting { collection, albums } enum HomePageSetting { collection, albums }
enum KeepScreenOn { never, viewerOnly, always } enum KeepScreenOn { never, viewerOnly, always }
enum DisplayRefreshRateMode { auto, highest, lowest } enum SlideshowInterval { s3, s5, s10, s30, s60 }
enum SlideshowVideoPlayback { skip, playMuted, playWithSound }
enum UnitSystem { metric, imperial } enum UnitSystem { metric, imperial }
enum VideoControls { play, playSeek, playOutside, none }
enum VideoLoopMode { never, shortOnly, always } enum VideoLoopMode { never, shortOnly, always }
enum VideoControls { play, playSeek, playOutside, none } enum ViewerTransition { slide, parallax, fade, zoomIn }

View file

@ -0,0 +1,36 @@
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart';
import 'enums.dart';
extension ExtraSlideshowInterval on SlideshowInterval {
String getName(BuildContext context) {
switch (this) {
case SlideshowInterval.s3:
return context.l10n.timeSeconds(3);
case SlideshowInterval.s5:
return context.l10n.timeSeconds(5);
case SlideshowInterval.s10:
return context.l10n.timeSeconds(10);
case SlideshowInterval.s30:
return context.l10n.timeSeconds(30);
case SlideshowInterval.s60:
return context.l10n.timeMinutes(1);
}
}
Duration getDuration() {
switch (this) {
case SlideshowInterval.s3:
return const Duration(seconds: 3);
case SlideshowInterval.s5:
return const Duration(seconds: 5);
case SlideshowInterval.s10:
return const Duration(seconds: 10);
case SlideshowInterval.s30:
return const Duration(seconds: 30);
case SlideshowInterval.s60:
return const Duration(minutes: 1);
}
}
}

View 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 SlideshowVideoPlayback {
String getName(BuildContext context) {
switch (this) {
case SlideshowVideoPlayback.skip:
return context.l10n.slideshowVideoPlaybackSkip;
case SlideshowVideoPlayback.playMuted:
return context.l10n.slideshowVideoPlaybackMuted;
case SlideshowVideoPlayback.playWithSound:
return context.l10n.slideshowVideoPlaybackWithSound;
}
}
}

View file

@ -0,0 +1,33 @@
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/controller.dart';
import 'package:flutter/widgets.dart';
import 'enums.dart';
extension ExtraViewerTransition on ViewerTransition {
String getName(BuildContext context) {
switch (this) {
case ViewerTransition.slide:
return context.l10n.viewerTransitionSlide;
case ViewerTransition.parallax:
return context.l10n.viewerTransitionParallax;
case ViewerTransition.fade:
return context.l10n.viewerTransitionFade;
case ViewerTransition.zoomIn:
return context.l10n.viewerTransitionZoomIn;
}
}
TransitionBuilder builder(PageController pageController, int index) {
switch (this) {
case ViewerTransition.slide:
return PageTransitionEffects.slide(pageController, index, parallax: false);
case ViewerTransition.parallax:
return PageTransitionEffects.slide(pageController, index, parallax: true);
case ViewerTransition.fade:
return PageTransitionEffects.fade(pageController, index, zoomIn: false);
case ViewerTransition.zoomIn:
return PageTransitionEffects.fade(pageController, index, zoomIn: true);
}
}
}

View file

@ -10,6 +10,7 @@ import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/enums.dart';
import 'package:aves/services/accessibility_service.dart'; import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/optional_event_channel.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -19,7 +20,7 @@ import 'package:flutter/services.dart';
final Settings settings = Settings._private(); final Settings settings = Settings._private();
class Settings extends ChangeNotifier { class Settings extends ChangeNotifier {
final EventChannel _platformSettingsChangeChannel = const EventChannel('deckers.thibault/aves/settings_change'); final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change');
final StreamController<SettingsChangedEvent> _updateStreamController = StreamController.broadcast(); final StreamController<SettingsChangedEvent> _updateStreamController = StreamController.broadcast();
Stream<SettingsChangedEvent> get updateStream => _updateStreamController.stream; Stream<SettingsChangedEvent> get updateStream => _updateStreamController.stream;
@ -42,15 +43,19 @@ class Settings extends ChangeNotifier {
static const isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed'; static const isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed';
static const isErrorReportingAllowedKey = 'is_crashlytics_enabled'; static const isErrorReportingAllowedKey = 'is_crashlytics_enabled';
static const localeKey = 'locale'; static const localeKey = 'locale';
static const displayRefreshRateModeKey = 'display_refresh_rate_mode';
static const themeBrightnessKey = 'theme_brightness';
static const themeColorModeKey = 'theme_color_mode';
static const catalogTimeZoneKey = 'catalog_time_zone'; static const catalogTimeZoneKey = 'catalog_time_zone';
static const tileExtentPrefixKey = 'tile_extent_'; static const tileExtentPrefixKey = 'tile_extent_';
static const tileLayoutPrefixKey = 'tile_layout_'; static const tileLayoutPrefixKey = 'tile_layout_';
static const entryRenamingPatternKey = 'entry_renaming_pattern'; static const entryRenamingPatternKey = 'entry_renaming_pattern';
static const topEntryIdsKey = 'top_entry_ids'; static const topEntryIdsKey = 'top_entry_ids';
// display
static const displayRefreshRateModeKey = 'display_refresh_rate_mode';
static const themeBrightnessKey = 'theme_brightness';
static const themeColorModeKey = 'theme_color_mode';
static const enableDynamicColorKey = 'dynamic_color';
static const enableBlurEffectKey = 'enable_overlay_blur_effect';
// navigation // navigation
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
static const keepScreenOnKey = 'keep_screen_on'; static const keepScreenOnKey = 'keep_screen_on';
@ -92,7 +97,6 @@ class Settings extends ChangeNotifier {
static const showOverlayInfoKey = 'show_overlay_info'; static const showOverlayInfoKey = 'show_overlay_info';
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview'; static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview';
static const enableOverlayBlurEffectKey = 'enable_overlay_blur_effect';
static const viewerUseCutoutKey = 'viewer_use_cutout'; static const viewerUseCutoutKey = 'viewer_use_cutout';
static const viewerMaxBrightnessKey = 'viewer_max_brightness'; static const viewerMaxBrightnessKey = 'viewer_max_brightness';
static const enableMotionPhotoAutoPlayKey = 'motion_photo_auto_play'; static const enableMotionPhotoAutoPlayKey = 'motion_photo_auto_play';
@ -134,6 +138,13 @@ class Settings extends ChangeNotifier {
// file picker // file picker
static const filePickerShowHiddenFilesKey = 'file_picker_show_hidden_files'; static const filePickerShowHiddenFilesKey = 'file_picker_show_hidden_files';
// slideshow
static const slideshowRepeatKey = 'slideshow_loop';
static const slideshowShuffleKey = 'slideshow_shuffle';
static const slideshowTransitionKey = 'slideshow_transition';
static const slideshowVideoPlaybackKey = 'slideshow_video_playback';
static const slideshowIntervalKey = 'slideshow_interval';
// platform settings // platform settings
// cf Android `Settings.System.ACCELEROMETER_ROTATION` // cf Android `Settings.System.ACCELEROMETER_ROTATION`
static const platformAccelerometerRotationKey = 'accelerometer_rotation'; static const platformAccelerometerRotationKey = 'accelerometer_rotation';
@ -161,7 +172,7 @@ class Settings extends ChangeNotifier {
Future<void> setContextualDefaults() async { Future<void> setContextualDefaults() async {
// performance // performance
final performanceClass = await deviceService.getPerformanceClass(); final performanceClass = await deviceService.getPerformanceClass();
enableOverlayBlurEffect = performanceClass >= 29; enableBlurEffect = performanceClass >= 29;
// availability // availability
final defaultMapStyle = mobileServices.defaultMapStyle; final defaultMapStyle = mobileServices.defaultMapStyle;
@ -187,8 +198,7 @@ class Settings extends ChangeNotifier {
set canUseAnalysisService(bool newValue) => setAndNotify(canUseAnalysisServiceKey, newValue); set canUseAnalysisService(bool newValue) => setAndNotify(canUseAnalysisServiceKey, newValue);
// TODO TLAD use `true` for transition (it's unset in v1.5.4), but replace by `SettingsDefaults.isInstalledAppAccessAllowed` in a later release bool get isInstalledAppAccessAllowed => getBoolOrDefault(isInstalledAppAccessAllowedKey, SettingsDefaults.isInstalledAppAccessAllowed);
bool get isInstalledAppAccessAllowed => getBoolOrDefault(isInstalledAppAccessAllowedKey, true);
set isInstalledAppAccessAllowed(bool newValue) => setAndNotify(isInstalledAppAccessAllowedKey, newValue); set isInstalledAppAccessAllowed(bool newValue) => setAndNotify(isInstalledAppAccessAllowedKey, newValue);
@ -249,18 +259,6 @@ class Settings extends ChangeNotifier {
return _appliedLocale!; return _appliedLocale!;
} }
DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values);
set displayRefreshRateMode(DisplayRefreshRateMode newValue) => setAndNotify(displayRefreshRateModeKey, newValue.toString());
AvesThemeBrightness get themeBrightness => getEnumOrDefault(themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values);
set themeBrightness(AvesThemeBrightness newValue) => setAndNotify(themeBrightnessKey, newValue.toString());
AvesThemeColorMode get themeColorMode => getEnumOrDefault(themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values);
set themeColorMode(AvesThemeColorMode newValue) => setAndNotify(themeColorModeKey, newValue.toString());
String get catalogTimeZone => getString(catalogTimeZoneKey) ?? ''; String get catalogTimeZone => getString(catalogTimeZoneKey) ?? '';
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue); set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
@ -281,6 +279,28 @@ class Settings extends ChangeNotifier {
set topEntryIds(List<int>? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList()); set topEntryIds(List<int>? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
// display
DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values);
set displayRefreshRateMode(DisplayRefreshRateMode newValue) => setAndNotify(displayRefreshRateModeKey, newValue.toString());
AvesThemeBrightness get themeBrightness => getEnumOrDefault(themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values);
set themeBrightness(AvesThemeBrightness newValue) => setAndNotify(themeBrightnessKey, newValue.toString());
AvesThemeColorMode get themeColorMode => getEnumOrDefault(themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values);
set themeColorMode(AvesThemeColorMode newValue) => setAndNotify(themeColorModeKey, newValue.toString());
bool get enableDynamicColor => getBoolOrDefault(enableDynamicColorKey, SettingsDefaults.enableDynamicColor);
set enableDynamicColor(bool newValue) => setAndNotify(enableDynamicColorKey, newValue);
bool get enableBlurEffect => getBoolOrDefault(enableBlurEffectKey, SettingsDefaults.enableBlurEffect);
set enableBlurEffect(bool newValue) => setAndNotify(enableBlurEffectKey, newValue);
// navigation // navigation
bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, SettingsDefaults.mustBackTwiceToExit); bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, SettingsDefaults.mustBackTwiceToExit);
@ -441,10 +461,6 @@ class Settings extends ChangeNotifier {
set showOverlayThumbnailPreview(bool newValue) => setAndNotify(showOverlayThumbnailPreviewKey, newValue); set showOverlayThumbnailPreview(bool newValue) => setAndNotify(showOverlayThumbnailPreviewKey, newValue);
bool get enableOverlayBlurEffect => getBoolOrDefault(enableOverlayBlurEffectKey, SettingsDefaults.enableOverlayBlurEffect);
set enableOverlayBlurEffect(bool newValue) => setAndNotify(enableOverlayBlurEffectKey, newValue);
bool get viewerUseCutout => getBoolOrDefault(viewerUseCutoutKey, SettingsDefaults.viewerUseCutout); bool get viewerUseCutout => getBoolOrDefault(viewerUseCutoutKey, SettingsDefaults.viewerUseCutout);
set viewerUseCutout(bool newValue) => setAndNotify(viewerUseCutoutKey, newValue); set viewerUseCutout(bool newValue) => setAndNotify(viewerUseCutoutKey, newValue);
@ -567,6 +583,28 @@ class Settings extends ChangeNotifier {
set filePickerShowHiddenFiles(bool newValue) => setAndNotify(filePickerShowHiddenFilesKey, newValue); set filePickerShowHiddenFiles(bool newValue) => setAndNotify(filePickerShowHiddenFilesKey, newValue);
// slideshow
bool get slideshowRepeat => getBoolOrDefault(slideshowRepeatKey, SettingsDefaults.slideshowRepeat);
set slideshowRepeat(bool newValue) => setAndNotify(slideshowRepeatKey, newValue);
bool get slideshowShuffle => getBoolOrDefault(slideshowShuffleKey, SettingsDefaults.slideshowShuffle);
set slideshowShuffle(bool newValue) => setAndNotify(slideshowShuffleKey, newValue);
ViewerTransition get slideshowTransition => getEnumOrDefault(slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values);
set slideshowTransition(ViewerTransition newValue) => setAndNotify(slideshowTransitionKey, newValue.toString());
SlideshowVideoPlayback get slideshowVideoPlayback => getEnumOrDefault(slideshowVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values);
set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => setAndNotify(slideshowVideoPlaybackKey, newValue.toString());
SlideshowInterval get slideshowInterval => getEnumOrDefault(slideshowIntervalKey, SettingsDefaults.slideshowInterval, SlideshowInterval.values);
set slideshowInterval(SlideshowInterval newValue) => setAndNotify(slideshowIntervalKey, newValue.toString());
// convenience methods // convenience methods
int? getInt(String key) => settingsStore.getInt(key); int? getInt(String key) => settingsStore.getInt(key);
@ -695,6 +733,8 @@ class Settings extends ChangeNotifier {
break; break;
case isInstalledAppAccessAllowedKey: case isInstalledAppAccessAllowedKey:
case isErrorReportingAllowedKey: case isErrorReportingAllowedKey:
case enableDynamicColorKey:
case enableBlurEffectKey:
case showBottomNavigationBarKey: case showBottomNavigationBarKey:
case mustBackTwiceToExitKey: case mustBackTwiceToExitKey:
case confirmDeleteForeverKey: case confirmDeleteForeverKey:
@ -713,7 +753,6 @@ class Settings extends ChangeNotifier {
case showOverlayInfoKey: case showOverlayInfoKey:
case showOverlayShootingDetailsKey: case showOverlayShootingDetailsKey:
case showOverlayThumbnailPreviewKey: case showOverlayThumbnailPreviewKey:
case enableOverlayBlurEffectKey:
case viewerUseCutoutKey: case viewerUseCutoutKey:
case viewerMaxBrightnessKey: case viewerMaxBrightnessKey:
case enableMotionPhotoAutoPlayKey: case enableMotionPhotoAutoPlayKey:
@ -724,6 +763,8 @@ class Settings extends ChangeNotifier {
case subtitleShowOutlineKey: case subtitleShowOutlineKey:
case saveSearchHistoryKey: case saveSearchHistoryKey:
case filePickerShowHiddenFilesKey: case filePickerShowHiddenFilesKey:
case slideshowRepeatKey:
case slideshowShuffleKey:
if (newValue is bool) { if (newValue is bool) {
settingsStore.setBool(key, newValue); settingsStore.setBool(key, newValue);
} else { } else {
@ -751,6 +792,9 @@ class Settings extends ChangeNotifier {
case unitSystemKey: case unitSystemKey:
case accessibilityAnimationsKey: case accessibilityAnimationsKey:
case timeToTakeActionKey: case timeToTakeActionKey:
case slideshowTransitionKey:
case slideshowVideoPlaybackKey:
case slideshowIntervalKey:
if (newValue is String) { if (newValue is String) {
settingsStore.setString(key, newValue); settingsStore.setString(key, newValue);
} else { } else {

View file

@ -32,7 +32,7 @@ class CollectionLens with ChangeNotifier {
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier(); final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
int? id; int? id;
bool listenToSource, groupBursts; bool listenToSource, groupBursts, fixedSort;
List<AvesEntry>? fixedSelection; List<AvesEntry>? fixedSelection;
List<AvesEntry> _filteredSortedEntries = []; List<AvesEntry> _filteredSortedEntries = [];
@ -45,6 +45,7 @@ class CollectionLens with ChangeNotifier {
this.id, this.id,
this.listenToSource = true, this.listenToSource = true,
this.groupBursts = true, this.groupBursts = true,
this.fixedSort = false,
this.fixedSelection, this.fixedSelection,
}) : filters = (filters ?? {}).whereNotNull().toSet(), }) : filters = (filters ?? {}).whereNotNull().toSet(),
sectionFactor = settings.collectionSectionFactor, sectionFactor = settings.collectionSectionFactor,
@ -203,6 +204,8 @@ class CollectionLens with ChangeNotifier {
} }
void _applySort() { void _applySort() {
if (fixedSort) return;
switch (sortFactor) { switch (sortFactor) {
case EntrySortFactor.date: case EntrySortFactor.date:
_filteredSortedEntries.sort(AvesEntry.compareByDate); _filteredSortedEntries.sort(AvesEntry.compareByDate);
@ -220,37 +223,43 @@ class CollectionLens with ChangeNotifier {
} }
void _applySection() { void _applySection() {
switch (sortFactor) { if (fixedSort) {
case EntrySortFactor.date: sections = Map.fromEntries([
switch (sectionFactor) { MapEntry(const SectionKey(), _filteredSortedEntries),
case EntryGroupFactor.album: ]);
sections = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); } else {
break; switch (sortFactor) {
case EntryGroupFactor.month: case EntrySortFactor.date:
sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken)); switch (sectionFactor) {
break; case EntryGroupFactor.album:
case EntryGroupFactor.day: sections = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken)); break;
break; case EntryGroupFactor.month:
case EntryGroupFactor.none: sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken));
sections = Map.fromEntries([ break;
MapEntry(const SectionKey(), _filteredSortedEntries), case EntryGroupFactor.day:
]); sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken));
break; break;
} case EntryGroupFactor.none:
break; sections = Map.fromEntries([
case EntrySortFactor.name: MapEntry(const SectionKey(), _filteredSortedEntries),
final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); ]);
sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!)); break;
break; }
case EntrySortFactor.rating: break;
sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); case EntrySortFactor.name:
break; final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
case EntrySortFactor.size: sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!));
sections = Map.fromEntries([ break;
MapEntry(const SectionKey(), _filteredSortedEntries), case EntrySortFactor.rating:
]); sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
break; break;
case EntrySortFactor.size:
sections = Map.fromEntries([
MapEntry(const SectionKey(), _filteredSortedEntries),
]);
break;
}
} }
sections = Map.unmodifiable(sections); sections = Map.unmodifiable(sections);
_sortedEntries = null; _sortedEntries = null;

View file

@ -416,6 +416,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
} }
} }
if (startAnalysisService) { if (startAnalysisService) {
// TODO TLAD [tiramisu] explain foreground service and request POST_NOTIFICATIONS permission
await AnalysisService.startService( await AnalysisService.startService(
force: force, force: force,
entryIds: entries?.map((entry) => entry.id).toList(), entryIds: entries?.map((entry) => entry.id).toList(),

View file

@ -0,0 +1,17 @@
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/material.dart';
enum WallpaperTarget { home, lock, homeLock }
extension ExtraWallpaperTarget on WallpaperTarget {
String getName(BuildContext context) {
switch (this) {
case WallpaperTarget.home:
return context.l10n.wallpaperTargetHome;
case WallpaperTarget.lock:
return context.l10n.wallpaperTargetLock;
case WallpaperTarget.homeLock:
return context.l10n.wallpaperTargetHomeLock;
}
}
}

View file

@ -44,6 +44,8 @@ class MimeTypes {
static const anyVideo = 'video/*'; static const anyVideo = 'video/*';
static const v3gpp = 'video/3gpp';
static const asf = 'video/x-ms-asf';
static const avi = 'video/avi'; static const avi = 'video/avi';
static const aviVnd = 'video/vnd.avi'; static const aviVnd = 'video/vnd.avi';
static const flv = 'video/flv'; static const flv = 'video/flv';
@ -56,6 +58,7 @@ class MimeTypes {
static const mpeg = 'video/mpeg'; static const mpeg = 'video/mpeg';
static const ogv = 'video/ogg'; static const ogv = 'video/ogg';
static const webm = 'video/webm'; static const webm = 'video/webm';
static const wmv = 'video/x-ms-wmv';
static const json = 'application/json'; static const json = 'application/json';
static const plainText = 'text/plain'; static const plainText = 'text/plain';
@ -76,7 +79,7 @@ class MimeTypes {
static const Set<String> _knownOpaqueImages = {jpeg}; static const Set<String> _knownOpaqueImages = {jpeg};
static const Set<String> _knownVideos = {avi, aviVnd, flv, flvX, mkv, mov, mp2t, mp2ts, mp4, mpeg, ogv, webm}; static const Set<String> _knownVideos = {v3gpp, asf, avi, aviVnd, flv, flvX, mkv, mov, mp2t, mp2ts, mp4, mpeg, ogv, webm, wmv};
static final Set<String> knownMediaTypes = { static final Set<String> knownMediaTypes = {
anyImage, anyImage,

View file

@ -0,0 +1,53 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
// adapted from Flutter `EventChannel` in `/services/platform_channel.dart`
// to use an `OptionalMethodChannel` when subscribing to events
class OptionalEventChannel extends EventChannel {
const OptionalEventChannel(super.name, [super.codec = const StandardMethodCodec(), super.binaryMessenger]);
@override
Stream<dynamic> receiveBroadcastStream([dynamic arguments]) {
final MethodChannel methodChannel = OptionalMethodChannel(name, codec);
late StreamController<dynamic> controller;
controller = StreamController<dynamic>.broadcast(onListen: () async {
binaryMessenger.setMessageHandler(name, (reply) async {
if (reply == null) {
await controller.close();
} else {
try {
controller.add(codec.decodeEnvelope(reply));
} on PlatformException catch (e) {
controller.addError(e);
}
}
return null;
});
try {
await methodChannel.invokeMethod<void>('listen', arguments);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('while activating platform stream on channel $name'),
));
}
}, onCancel: () async {
binaryMessenger.setMessageHandler(name, null);
try {
await methodChannel.invokeMethod<void>('cancel', arguments);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('while de-activating platform stream on channel $name'),
));
}
});
return controller.stream;
}
}

View file

@ -22,7 +22,11 @@ class GeocodingService {
}); });
return (result as List).cast<Map>().map(Address.fromMap).toList(); return (result as List).cast<Map>().map(Address.fromMap).toList();
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
if (e.code != 'getAddress-empty' && e.code != 'getAddress-network') { if (!{
'getAddress-empty',
'getAddress-network',
'getAddress-unavailable',
}.contains(e.code)) {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
} }

View file

@ -0,0 +1,23 @@
import 'dart:typed_data';
import 'package:aves/model/wallpaper_target.dart';
import 'package:aves/services/common/services.dart';
import 'package:flutter/services.dart';
class WallpaperService {
static const platform = MethodChannel('deckers.thibault/aves/wallpaper');
static Future<bool> set(Uint8List bytes, WallpaperTarget target) async {
try {
await platform.invokeMethod('setWallpaper', <String, dynamic>{
'bytes': bytes,
'home': {WallpaperTarget.home, WallpaperTarget.homeLock}.contains(target),
'lock': {WallpaperTarget.lock, WallpaperTarget.homeLock}.contains(target),
});
return true;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return false;
}
}

View file

@ -104,6 +104,7 @@ class AIcons {
static const IconData setCover = MdiIcons.imageEditOutline; static const IconData setCover = MdiIcons.imageEditOutline;
static const IconData share = Icons.share_outlined; static const IconData share = Icons.share_outlined;
static const IconData show = Icons.visibility_outlined; static const IconData show = Icons.visibility_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.pie_chart_outline_outlined;
static const IconData streams = Icons.translate_outlined; static const IconData streams = Icons.translate_outlined;

View file

@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class Themes { class Themes {
static const _accentColor = Colors.indigoAccent; static const defaultAccent = Colors.indigoAccent;
static const _tooltipTheme = TooltipThemeData( static const _tooltipTheme = TooltipThemeData(
verticalOffset: 32, verticalOffset: 32,
@ -19,10 +19,10 @@ class Themes {
fontFeatures: [FontFeature.enable('smcp')], fontFeatures: [FontFeature.enable('smcp')],
); );
static const _snackBarTheme = SnackBarThemeData( static SnackBarThemeData _snackBarTheme(Color accentColor) => SnackBarThemeData(
actionTextColor: _accentColor, actionTextColor: accentColor,
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
); );
static final _typography = Typography.material2018(platform: TargetPlatform.android); static final _typography = Typography.material2018(platform: TargetPlatform.android);
@ -35,49 +35,49 @@ class Themes {
static const _lightSecondLayer = Color(0xFFF5F5F5); // aka `Colors.grey[100]` static const _lightSecondLayer = Color(0xFFF5F5F5); // aka `Colors.grey[100]`
static const _lightThirdLayer = Color(0xFFEEEEEE); // aka `Colors.grey[200]` static const _lightThirdLayer = Color(0xFFEEEEEE); // aka `Colors.grey[200]`
static final lightTheme = ThemeData( static ThemeData lightTheme(Color accentColor) => ThemeData(
colorScheme: ColorScheme.light( colorScheme: ColorScheme.light(
primary: _accentColor, primary: accentColor,
secondary: _accentColor, secondary: accentColor,
onPrimary: _lightBodyColor, onPrimary: _lightBodyColor,
onSecondary: _lightBodyColor, onSecondary: _lightBodyColor,
), ),
brightness: Brightness.light, brightness: Brightness.light,
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
canvasColor: _lightSecondLayer, canvasColor: _lightSecondLayer,
scaffoldBackgroundColor: _lightFirstLayer, scaffoldBackgroundColor: _lightFirstLayer,
// `cardColor` is used by `ExpansionPanel` // `cardColor` is used by `ExpansionPanel`
cardColor: _lightSecondLayer, cardColor: _lightSecondLayer,
dialogBackgroundColor: _lightSecondLayer, dialogBackgroundColor: _lightSecondLayer,
indicatorColor: _accentColor, indicatorColor: accentColor,
toggleableActiveColor: _accentColor, toggleableActiveColor: accentColor,
typography: _typography, typography: _typography,
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
backgroundColor: _lightFirstLayer, backgroundColor: _lightFirstLayer,
// `foregroundColor` is used by icons // `foregroundColor` is used by icons
foregroundColor: _lightActionIconColor, foregroundColor: _lightActionIconColor,
// `titleTextStyle.color` is used by text // `titleTextStyle.color` is used by text
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _lightTitleColor), titleTextStyle: _appBarTitleTextStyle.copyWith(color: _lightTitleColor),
systemOverlayStyle: SystemUiOverlayStyle.dark, systemOverlayStyle: SystemUiOverlayStyle.dark,
), ),
listTileTheme: const ListTileThemeData( listTileTheme: const ListTileThemeData(
iconColor: _lightActionIconColor, iconColor: _lightActionIconColor,
), ),
popupMenuTheme: const PopupMenuThemeData( popupMenuTheme: const PopupMenuThemeData(
color: _lightSecondLayer, color: _lightSecondLayer,
), ),
snackBarTheme: _snackBarTheme, snackBarTheme: _snackBarTheme(accentColor),
tabBarTheme: TabBarTheme( tabBarTheme: TabBarTheme(
labelColor: _lightTitleColor, labelColor: _lightTitleColor,
unselectedLabelColor: Colors.black54, unselectedLabelColor: Colors.black54,
), ),
textButtonTheme: TextButtonThemeData( textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom( style: TextButton.styleFrom(
primary: _lightLabelColor, primary: _lightLabelColor,
), ),
), ),
tooltipTheme: _tooltipTheme, tooltipTheme: _tooltipTheme,
); );
static final _darkThemeTypo = _typography.white; static final _darkThemeTypo = _typography.white;
static final _darkTitleColor = _darkThemeTypo.titleMedium!.color!; static final _darkTitleColor = _darkThemeTypo.titleMedium!.color!;
@ -87,71 +87,74 @@ class Themes {
static const _darkSecondLayer = Color(0xFF363636); static const _darkSecondLayer = Color(0xFF363636);
static const _darkThirdLayer = Color(0xFF424242); // aka `Colors.grey[800]` static const _darkThirdLayer = Color(0xFF424242); // aka `Colors.grey[800]`
static final darkTheme = ThemeData( static ThemeData darkTheme(Color accentColor) => ThemeData(
colorScheme: ColorScheme.dark( colorScheme: ColorScheme.dark(
primary: _accentColor, primary: accentColor,
secondary: _accentColor, secondary: accentColor,
// surface color is used by the date/time pickers // surface color is used by the date/time pickers
surface: Colors.grey.shade800, surface: Colors.grey.shade800,
onPrimary: _darkBodyColor, onPrimary: _darkBodyColor,
onSecondary: _darkBodyColor, onSecondary: _darkBodyColor,
), ),
brightness: Brightness.dark, brightness: Brightness.dark,
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
canvasColor: _darkSecondLayer, canvasColor: _darkSecondLayer,
scaffoldBackgroundColor: _darkFirstLayer, scaffoldBackgroundColor: _darkFirstLayer,
// `cardColor` is used by `ExpansionPanel` // `cardColor` is used by `ExpansionPanel`
cardColor: _darkSecondLayer, cardColor: _darkSecondLayer,
dialogBackgroundColor: _darkSecondLayer, dialogBackgroundColor: _darkSecondLayer,
indicatorColor: _accentColor, indicatorColor: accentColor,
toggleableActiveColor: _accentColor, toggleableActiveColor: accentColor,
typography: _typography, typography: _typography,
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
backgroundColor: _darkFirstLayer, backgroundColor: _darkFirstLayer,
// `foregroundColor` is used by icons // `foregroundColor` is used by icons
foregroundColor: _darkTitleColor, foregroundColor: _darkTitleColor,
// `titleTextStyle.color` is used by text // `titleTextStyle.color` is used by text
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _darkTitleColor), titleTextStyle: _appBarTitleTextStyle.copyWith(color: _darkTitleColor),
systemOverlayStyle: SystemUiOverlayStyle.light, systemOverlayStyle: SystemUiOverlayStyle.light,
), ),
popupMenuTheme: const PopupMenuThemeData( popupMenuTheme: const PopupMenuThemeData(
color: _darkSecondLayer, color: _darkSecondLayer,
), ),
snackBarTheme: _snackBarTheme.copyWith( snackBarTheme: _snackBarTheme(accentColor).copyWith(
backgroundColor: Colors.grey.shade800, backgroundColor: Colors.grey.shade800,
contentTextStyle: TextStyle( contentTextStyle: TextStyle(
color: _darkBodyColor, color: _darkBodyColor,
), ),
), ),
tabBarTheme: TabBarTheme( tabBarTheme: TabBarTheme(
labelColor: _darkTitleColor, labelColor: _darkTitleColor,
), ),
textButtonTheme: TextButtonThemeData( textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom( style: TextButton.styleFrom(
primary: _darkLabelColor, primary: _darkLabelColor,
), ),
), ),
tooltipTheme: _tooltipTheme, tooltipTheme: _tooltipTheme,
); );
static const _blackFirstLayer = Colors.black; static const _blackFirstLayer = Colors.black;
static const _blackSecondLayer = Color(0xFF212121); // aka `Colors.grey[900]` static const _blackSecondLayer = Color(0xFF212121); // aka `Colors.grey[900]`
static const _blackThirdLayer = Color(0xFF303030); // aka `Colors.grey[850]` static const _blackThirdLayer = Color(0xFF303030); // aka `Colors.grey[850]`
static final blackTheme = darkTheme.copyWith( static ThemeData blackTheme(Color accentColor) {
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` final baseTheme = darkTheme(accentColor);
canvasColor: _blackSecondLayer, return baseTheme.copyWith(
scaffoldBackgroundColor: _blackFirstLayer, // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
// `cardColor` is used by `ExpansionPanel` canvasColor: _blackSecondLayer,
cardColor: _blackSecondLayer, scaffoldBackgroundColor: _blackFirstLayer,
dialogBackgroundColor: _blackSecondLayer, // `cardColor` is used by `ExpansionPanel`
appBarTheme: darkTheme.appBarTheme.copyWith( cardColor: _blackSecondLayer,
backgroundColor: _blackFirstLayer, dialogBackgroundColor: _blackSecondLayer,
), appBarTheme: baseTheme.appBarTheme.copyWith(
popupMenuTheme: darkTheme.popupMenuTheme.copyWith( backgroundColor: _blackFirstLayer,
color: _blackSecondLayer, ),
), popupMenuTheme: baseTheme.popupMenuTheme.copyWith(
); color: _blackSecondLayer,
),
);
}
static Color overlayBackgroundColor({ static Color overlayBackgroundColor({
required Brightness brightness, required Brightness brightness,

View file

@ -108,6 +108,11 @@ class Constants {
licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/device_info_plus/device_info_plus/LICENSE', licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/device_info_plus/device_info_plus/LICENSE',
sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus', sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus',
), ),
Dependency(
name: 'Dynamic Color',
license: 'BSD 3-Clause',
sourceUrl: 'https://github.com/material-foundation/material-dynamic-color-flutter',
),
Dependency( Dependency(
name: 'fijkplayer (Aves fork)', name: 'fijkplayer (Aves fork)',
license: 'MIT', license: 'MIT',
@ -337,6 +342,12 @@ class Constants {
license: 'Apache 2.0', license: 'Apache 2.0',
sourceUrl: 'https://github.com/jifalops/dart-latlong', sourceUrl: 'https://github.com/jifalops/dart-latlong',
), ),
Dependency(
name: 'Material Color Utilities',
license: 'Apache 2.0',
licenseUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart/LICENSE',
sourceUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart',
),
Dependency( Dependency(
name: 'Path', name: 'Path',
license: 'BSD 3-Clause', license: 'BSD 3-Clause',

View file

@ -1,16 +1,16 @@
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
const _kiloDivider = 1024; const kilo = 1024;
const _megaDivider = _kiloDivider * _kiloDivider; const mega = kilo * kilo;
const _gigaDivider = _megaDivider * _kiloDivider; const giga = mega * kilo;
const _teraDivider = _gigaDivider * _kiloDivider; const tera = giga * kilo;
String formatFileSize(String locale, int size, {int round = 2}) { String formatFileSize(String locale, int size, {int round = 2}) {
if (size < _kiloDivider) return '$size B'; if (size < kilo) return '$size B';
final formatter = NumberFormat('0${round > 0 ? '.${'0' * round}' : ''}', locale); final formatter = NumberFormat('0${round > 0 ? '.${'0' * round}' : ''}', locale);
if (size < _megaDivider) return '${formatter.format(size / _kiloDivider)} KB'; if (size < mega) return '${formatter.format(size / kilo)} KB';
if (size < _gigaDivider) return '${formatter.format(size / _megaDivider)} MB'; if (size < giga) return '${formatter.format(size / mega)} MB';
if (size < _teraDivider) return '${formatter.format(size / _gigaDivider)} GB'; if (size < tera) return '${formatter.format(size / giga)} GB';
return '${formatter.format(size / _teraDivider)} TB'; return '${formatter.format(size / tera)} TB';
} }

View file

@ -13,6 +13,7 @@ class AboutCredits extends StatelessWidget {
'Español (México)': 'n-berenice', 'Español (México)': 'n-berenice',
'Italiano': 'glemco', 'Italiano': 'glemco',
'Português (Brasil)': 'Jonatas De Almeida Barros', 'Português (Brasil)': 'Jonatas De Almeida Barros',
'Türkçe': 'metezd',
'Русский': 'D3ZOXY', 'Русский': 'D3ZOXY',
'日本語': 'Maki', '日本語': 'Maki',
'简体中文': '小默, Aerowolf', '简体中文': '小默, Aerowolf',

View file

@ -5,6 +5,7 @@ import 'package:aves/app_flavor.dart';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/l10n/l10n.dart'; import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/device.dart'; import 'package:aves/model/device.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart'; import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
@ -15,6 +16,7 @@ import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/model/source/media_store_source.dart';
import 'package:aves/services/accessibility_service.dart'; import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/optional_event_channel.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
@ -30,11 +32,13 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
import 'package:aves/widgets/home_page.dart'; import 'package:aves/widgets/home_page.dart';
import 'package:aves/widgets/welcome_page.dart'; import 'package:aves/widgets/welcome_page.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:fijkplayer/fijkplayer.dart'; import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:material_color_utilities/material_color_utilities.dart';
import 'package:overlay_support/overlay_support.dart'; import 'package:overlay_support/overlay_support.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -70,6 +74,7 @@ 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 Future<void> _appSetup; late Future<void> _appSetup;
late Future<CorePalette?> _dynamicColorPaletteLoader;
final _mediaStoreSource = MediaStoreSource(); final _mediaStoreSource = MediaStoreSource();
final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay); final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay);
final Set<String> changedUris = {}; final Set<String> changedUris = {};
@ -77,10 +82,10 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
// 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
List<NavigatorObserver> _navigatorObservers = [AvesApp.pageRouteObserver]; List<NavigatorObserver> _navigatorObservers = [AvesApp.pageRouteObserver];
final EventChannel _mediaStoreChangeChannel = const EventChannel('deckers.thibault/aves/media_store_change'); final EventChannel _mediaStoreChangeChannel = const OptionalEventChannel('deckers.thibault/aves/media_store_change');
final EventChannel _newIntentChannel = const EventChannel('deckers.thibault/aves/intent'); final EventChannel _newIntentChannel = const OptionalEventChannel('deckers.thibault/aves/intent');
final EventChannel _analysisCompletionChannel = const EventChannel('deckers.thibault/aves/analysis_events'); final EventChannel _analysisCompletionChannel = const OptionalEventChannel('deckers.thibault/aves/analysis_events');
final EventChannel _errorChannel = const EventChannel('deckers.thibault/aves/error'); final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error');
Widget getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage(); Widget getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage();
@ -89,6 +94,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
super.initState(); super.initState();
EquatableConfig.stringify = true; EquatableConfig.stringify = true;
_appSetup = _setup(); _appSetup = _setup();
_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?));
_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()); _analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion());
@ -120,16 +126,18 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
: Scaffold( : Scaffold(
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(), body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
); );
return Selector<Settings, Tuple3<Locale?, bool, AvesThemeBrightness>>( return Selector<Settings, Tuple4<Locale?, bool, AvesThemeBrightness, bool>>(
selector: (context, s) => Tuple3( selector: (context, s) => Tuple4(
s.locale, s.locale,
s.initialized ? s.accessibilityAnimations.animate : true, s.initialized ? s.accessibilityAnimations.animate : true,
s.initialized ? s.themeBrightness : AvesThemeBrightness.system, s.initialized ? s.themeBrightness : SettingsDefaults.themeBrightness,
s.initialized ? s.enableDynamicColor : SettingsDefaults.enableDynamicColor,
), ),
builder: (context, s, child) { builder: (context, s, child) {
final settingsLocale = s.item1; final settingsLocale = s.item1;
final areAnimationsEnabled = s.item2; final areAnimationsEnabled = s.item2;
final themeBrightness = s.item3; final themeBrightness = s.item3;
final enableDynamicColor = s.item4;
final pageTransitionsTheme = areAnimationsEnabled final pageTransitionsTheme = areAnimationsEnabled
// Flutter has various page transition implementations for Android: // Flutter has various page transition implementations for Android:
@ -144,27 +152,42 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
// strip page transitions used by `MaterialPageRoute` // strip page transitions used by `MaterialPageRoute`
: const DirectPageTransitionsTheme(); : const DirectPageTransitionsTheme();
return MaterialApp( return FutureBuilder<CorePalette?>(
navigatorKey: AvesApp.navigatorKey, future: _dynamicColorPaletteLoader,
home: home, builder: (context, snapshot) {
navigatorObservers: _navigatorObservers, const defaultAccent = Themes.defaultAccent;
builder: (context, child) => AvesColorsProvider( Color lightAccent = defaultAccent, darkAccent = defaultAccent;
child: Theme( if (enableDynamicColor) {
data: Theme.of(context).copyWith( // `DynamicColorBuilder` from package `dynamic_color` provides light/dark
pageTransitionsTheme: pageTransitionsTheme, // palettes with a primary color from tones too dark/light (40/80),
// so we derive the color with adjusted tones (60/70)
final tonalPalette = snapshot.data?.primary;
lightAccent = Color(tonalPalette?.get(60) ?? defaultAccent.value);
darkAccent = Color(tonalPalette?.get(70) ?? defaultAccent.value);
}
return MaterialApp(
navigatorKey: AvesApp.navigatorKey,
home: home,
navigatorObservers: _navigatorObservers,
builder: (context, child) => AvesColorsProvider(
child: Theme(
data: Theme.of(context).copyWith(
pageTransitionsTheme: pageTransitionsTheme,
),
child: child!,
),
), ),
child: child!, onGenerateTitle: (context) => context.l10n.appName,
), theme: Themes.lightTheme(lightAccent),
), darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent) : Themes.darkTheme(darkAccent),
onGenerateTitle: (context) => context.l10n.appName, themeMode: themeBrightness.appThemeMode,
theme: Themes.lightTheme, locale: settingsLocale,
darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme : Themes.darkTheme, localizationsDelegates: AppLocalizations.localizationsDelegates,
themeMode: themeBrightness.appThemeMode, supportedLocales: AppLocalizations.supportedLocales,
locale: settingsLocale, // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906
localizationsDelegates: AppLocalizations.localizationsDelegates, scrollBehavior: StretchMaterialScrollBehavior(),
supportedLocales: AppLocalizations.supportedLocales, );
// TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906 },
scrollBehavior: StretchMaterialScrollBehavior(),
); );
}, },
); );
@ -207,6 +230,8 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
break; break;
case AppMode.pickMediaInternal: case AppMode.pickMediaInternal:
case AppMode.pickFilterInternal: case AppMode.pickFilterInternal:
case AppMode.setWallpaper:
case AppMode.slideshow:
case AppMode.view: case AppMode.view:
break; break;
} }
@ -223,7 +248,14 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
if (!settings.initialized) return; if (!settings.initialized) return;
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
final screenSize = window.physicalSize / window.devicePixelRatio; final Size screenSize;
try {
screenSize = window.physicalSize / window.devicePixelRatio;
} catch (error) {
// view may no longer be usable
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;

View file

@ -284,6 +284,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: PopupMenuItemExpansionPanel<EntrySetAction>( child: PopupMenuItemExpansionPanel<EntrySetAction>(
enabled: canApplyEditActions, enabled: canApplyEditActions,
value: 'edit',
icon: AIcons.edit, icon: AIcons.edit,
title: context.l10n.collectionActionEdit, title: context.l10n.collectionActionEdit,
items: [ items: [
@ -477,6 +478,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
case EntrySetAction.addShortcut: case EntrySetAction.addShortcut:
// browsing or selecting // browsing or selecting
case EntrySetAction.map: case EntrySetAction.map:
case EntrySetAction.slideshow:
case EntrySetAction.stats: case EntrySetAction.stats:
case EntrySetAction.rescan: case EntrySetAction.rescan:
case EntrySetAction.emptyBin: case EntrySetAction.emptyBin:

View file

@ -29,6 +29,7 @@ import 'package:aves/widgets/common/grid/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';
import 'package:aves/widgets/common/identity/buttons.dart';
import 'package:aves/widgets/common/identity/empty.dart'; 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';
@ -39,6 +40,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -97,6 +99,7 @@ class _CollectionGridContent extends StatelessWidget {
final sectionedListLayoutProvider = ValueListenableBuilder<double>( final sectionedListLayoutProvider = ValueListenableBuilder<double>(
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier), valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
builder: (context, thumbnailExtent, child) { builder: (context, thumbnailExtent, child) {
assert(thumbnailExtent > 0);
return Selector<TileExtentController, Tuple4<double, int, double, double>>( return Selector<TileExtentController, Tuple4<double, int, double, double>>(
selector: (context, c) => Tuple4(c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding), selector: (context, c) => Tuple4(c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding),
builder: (context, c, child) { builder: (context, c, child) {
@ -305,13 +308,15 @@ class _CollectionScrollView extends StatefulWidget {
State<_CollectionScrollView> createState() => _CollectionScrollViewState(); State<_CollectionScrollView> createState() => _CollectionScrollViewState();
} }
class _CollectionScrollViewState extends State<_CollectionScrollView> { class _CollectionScrollViewState extends State<_CollectionScrollView> with WidgetsBindingObserver {
Timer? _scrollMonitoringTimer; Timer? _scrollMonitoringTimer;
bool _checkingStoragePermission = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_registerWidget(widget); _registerWidget(widget);
WidgetsBinding.instance.addObserver(this);
} }
@override @override
@ -323,6 +328,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this);
_unregisterWidget(widget); _unregisterWidget(widget);
_stopScrollMonitoringTimer(); _stopScrollMonitoringTimer();
super.dispose(); super.dispose();
@ -340,6 +346,26 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
widget.scrollController.removeListener(_onScrollChange); widget.scrollController.removeListener(_onScrollChange);
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
break;
case AppLifecycleState.resumed:
if (_checkingStoragePermission) {
_checkingStoragePermission = false;
_isStoragePermissionGranted.then((granted) {
if (granted) {
widget.collection.source.init();
}
});
}
break;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final scrollView = _buildScrollView(widget.appBar, widget.collection); final scrollView = _buildScrollView(widget.appBar, widget.collection);
@ -423,23 +449,47 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
valueListenable: collection.source.stateNotifier, valueListenable: collection.source.stateNotifier,
builder: (context, sourceState, child) { builder: (context, sourceState, child) {
if (sourceState == SourceState.loading) { if (sourceState == SourceState.loading) {
return const SizedBox.shrink(); return const SizedBox();
} }
if (collection.filters.any((filter) => filter is FavouriteFilter)) {
return EmptyContent( return FutureBuilder<bool>(
icon: AIcons.favourite, future: _isStoragePermissionGranted,
text: context.l10n.collectionEmptyFavourites, builder: (context, snapshot) {
); final granted = snapshot.data ?? true;
} Widget? bottom = granted
if (collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.anyVideo)) { ? null
return EmptyContent( : Padding(
icon: AIcons.video, padding: const EdgeInsets.only(top: 16),
text: context.l10n.collectionEmptyVideos, child: AvesOutlinedButton(
); label: context.l10n.collectionEmptyGrantAccessButtonLabel,
} onPressed: () async {
return EmptyContent( if (await openAppSettings()) {
icon: AIcons.image, _checkingStoragePermission = true;
text: context.l10n.collectionEmptyImages, }
},
),
);
if (collection.filters.any((filter) => filter is FavouriteFilter)) {
return EmptyContent(
icon: AIcons.favourite,
text: context.l10n.collectionEmptyFavourites,
bottom: bottom,
);
}
if (collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.anyVideo)) {
return EmptyContent(
icon: AIcons.video,
text: context.l10n.collectionEmptyVideos,
bottom: bottom,
);
}
return EmptyContent(
icon: AIcons.image,
text: context.l10n.collectionEmptyImages,
bottom: bottom,
);
},
); );
}, },
); );
@ -519,4 +569,6 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
} }
return crumbs; return crumbs;
} }
Future<bool> get _isStoragePermissionGranted => Permission.storage.status.then((status) => status.isGranted);
} }

View file

@ -35,6 +35,7 @@ import 'package:aves/widgets/dialogs/entry_editors/rename_entry_set_dialog.dart'
import 'package:aves/widgets/map/map_page.dart'; import 'package:aves/widgets/map/map_page.dart';
import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/search/search_delegate.dart';
import 'package:aves/widgets/stats/stats_page.dart'; import 'package:aves/widgets/stats/stats_page.dart';
import 'package:aves/widgets/viewer/slideshow_page.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
@ -73,6 +74,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
return appMode == AppMode.main && isTrash; return appMode == AppMode.main && isTrash;
// browsing or selecting // browsing or selecting
case EntrySetAction.map: case EntrySetAction.map:
case EntrySetAction.slideshow:
case EntrySetAction.stats: case EntrySetAction.stats:
return appMode == AppMode.main; return appMode == AppMode.main;
case EntrySetAction.rescan: case EntrySetAction.rescan:
@ -124,6 +126,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.emptyBin: case EntrySetAction.emptyBin:
return !isSelecting && hasItems; return !isSelecting && hasItems;
case EntrySetAction.map: case EntrySetAction.map:
case EntrySetAction.slideshow:
case EntrySetAction.stats: case EntrySetAction.stats:
case EntrySetAction.rescan: case EntrySetAction.rescan:
return (!isSelecting && hasItems) || (isSelecting && hasSelection); return (!isSelecting && hasItems) || (isSelecting && hasSelection);
@ -169,6 +172,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.map: case EntrySetAction.map:
_goToMap(context); _goToMap(context);
break; break;
case EntrySetAction.slideshow:
_goToSlideshow(context);
break;
case EntrySetAction.stats: case EntrySetAction.stats:
_goToStats(context); _goToStats(context);
break; break;
@ -543,6 +549,27 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
); );
} }
void _goToSlideshow(BuildContext context) {
final collection = context.read<CollectionLens>();
final entries = _getTargetItems(context);
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: SlideshowPage.routeName),
builder: (context) {
return SlideshowPage(
collection: CollectionLens(
source: collection.source,
filters: collection.filters,
fixedSelection: entries.toList(),
),
);
},
),
);
}
void _goToStats(BuildContext context) { void _goToStats(BuildContext context) {
final collection = context.read<CollectionLens>(); final collection = context.read<CollectionLens>();
final entries = _getTargetItems(context); final entries = _getTargetItems(context);

View file

@ -54,6 +54,8 @@ class InteractiveTile extends StatelessWidget {
Navigator.pop(context, entry); Navigator.pop(context, entry);
break; break;
case AppMode.pickFilterInternal: case AppMode.pickFilterInternal:
case AppMode.setWallpaper:
case AppMode.slideshow:
case AppMode.view: case AppMode.view:
break; break;
} }

View file

@ -122,7 +122,7 @@ mixin FeedbackMixin {
Future<void> showOpReport<T>({ Future<void> showOpReport<T>({
required BuildContext context, required BuildContext context,
required Stream<T> opStream, required Stream<T> opStream,
required int itemCount, int? itemCount,
VoidCallback? onCancel, VoidCallback? onCancel,
void Function(Set<T> processed)? onDone, void Function(Set<T> processed)? onDone,
}) { }) {
@ -144,7 +144,7 @@ mixin FeedbackMixin {
class ReportOverlay<T> extends StatefulWidget { class ReportOverlay<T> extends StatefulWidget {
final Stream<T> opStream; final Stream<T> opStream;
final int itemCount; final int? itemCount;
final VoidCallback? onCancel; final VoidCallback? onCancel;
final void Function(Set<T> processed) onDone; final void Function(Set<T> processed) onDone;
@ -212,7 +212,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
builder: (context, snapshot) { builder: (context, snapshot) {
final processedCount = processed.length.toDouble(); final processedCount = processed.length.toDouble();
final total = widget.itemCount; final total = widget.itemCount;
final percent = total != 0 ? min(1.0, processedCount / total) : 1.0; final percent = total == null || total == 0 ? 0.0 : min(1.0, processedCount / total);
return FadeTransition( return FadeTransition(
opacity: _animation, opacity: _animation,
child: Stack( child: Stack(
@ -243,10 +243,12 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(.2), backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(.2),
progressColor: progressColor, progressColor: progressColor,
animation: animate, animation: animate,
center: Text( center: total != null
NumberFormat.percentPattern().format(percent), ? Text(
style: const TextStyle(fontSize: fontSize), NumberFormat.percentPattern().format(percent),
), style: const TextStyle(fontSize: fontSize),
)
: null,
animateFromLastPercent: true, animateFromLastPercent: true,
), ),
if (widget.onCancel != null) if (widget.onCancel != null)
@ -305,12 +307,13 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
if (start != null && stop != null) { if (start != null && stop != null) {
_totalDurationMillis = stop.difference(start).inMilliseconds; _totalDurationMillis = stop.difference(start).inMilliseconds;
final remainingDuration = stop.difference(DateTime.now()); final remainingDuration = stop.difference(DateTime.now());
final effectiveDuration = remainingDuration > Duration.zero ? remainingDuration : const Duration(milliseconds: 1);
_animationController = AnimationController( _animationController = AnimationController(
duration: remainingDuration, duration: effectiveDuration,
vsync: this, vsync: this,
); );
_remainingDurationMillis = IntTween( _remainingDurationMillis = IntTween(
begin: remainingDuration.inMilliseconds, begin: effectiveDuration.inMilliseconds,
end: 0, end: 0,
).animate(CurvedAnimation( ).animate(CurvedAnimation(
parent: _animationController!, parent: _animationController!,

View file

@ -38,7 +38,7 @@ class AvesHighlightView extends StatelessWidget {
this.padding, this.padding,
this.textStyle, this.textStyle,
int tabSize = 8, // TODO: https://github.com/flutter/flutter/issues/50087 int tabSize = 8, // TODO: https://github.com/flutter/flutter/issues/50087
}) : source = input.replaceAll('\t', ' ' * tabSize); }) : source = input.replaceAll('\t', ' ' * tabSize);
List<TextSpan> _convert(List<Node> nodes) { List<TextSpan> _convert(List<Node> nodes) {
final spans = <TextSpan>[]; final spans = <TextSpan>[];

View file

@ -55,25 +55,27 @@ class MenuIconTheme extends StatelessWidget {
class PopupMenuItemExpansionPanel<T> extends StatefulWidget { class PopupMenuItemExpansionPanel<T> extends StatefulWidget {
final bool enabled; final bool enabled;
final String value;
final ValueNotifier<String?> expandedNotifier;
final IconData icon; final IconData icon;
final String title; final String title;
final List<PopupMenuEntry<T>> items; final List<PopupMenuEntry<T>> items;
const PopupMenuItemExpansionPanel({ PopupMenuItemExpansionPanel({
super.key, super.key,
this.enabled = true, this.enabled = true,
required this.value,
ValueNotifier<String?>? expandedNotifier,
required this.icon, required this.icon,
required this.title, required this.title,
required this.items, required this.items,
}); }) : expandedNotifier = expandedNotifier ?? ValueNotifier(null);
@override @override
State<PopupMenuItemExpansionPanel<T>> createState() => _PopupMenuItemExpansionPanelState<T>(); State<PopupMenuItemExpansionPanel<T>> createState() => _PopupMenuItemExpansionPanelState<T>();
} }
class _PopupMenuItemExpansionPanelState<T> extends State<PopupMenuItemExpansionPanel<T>> { class _PopupMenuItemExpansionPanelState<T> extends State<PopupMenuItemExpansionPanel<T>> {
bool _isExpanded = false;
// ref `_kMenuHorizontalPadding` used in `PopupMenuItem` // ref `_kMenuHorizontalPadding` used in `PopupMenuItem`
static const double _horizontalPadding = 16; static const double _horizontalPadding = 16;
@ -86,38 +88,43 @@ class _PopupMenuItemExpansionPanelState<T> extends State<PopupMenuItemExpansionP
} }
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation); final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
Widget child = ExpansionPanelList( Widget child = ValueListenableBuilder<String?>(
expansionCallback: (index, isExpanded) { valueListenable: widget.expandedNotifier,
setState(() => _isExpanded = !isExpanded); builder: (context, expandedValue, child) {
}, return ExpansionPanelList(
animationDuration: animationDuration, expansionCallback: (index, isExpanded) {
expandedHeaderPadding: EdgeInsets.zero, widget.expandedNotifier.value = isExpanded ? null : widget.value;
elevation: 0, },
children: [ animationDuration: animationDuration,
ExpansionPanel( expandedHeaderPadding: EdgeInsets.zero,
headerBuilder: (context, isExpanded) => DefaultTextStyle( elevation: 0,
style: style, children: [
child: Padding( ExpansionPanel(
padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding), headerBuilder: (context, isExpanded) => DefaultTextStyle(
child: MenuRow( style: style,
text: widget.title, child: Padding(
icon: Icon(widget.icon), padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
child: MenuRow(
text: widget.title,
icon: Icon(widget.icon),
),
),
), ),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const PopupMenuDivider(height: 0),
...widget.items,
const PopupMenuDivider(height: 0),
],
),
isExpanded: expandedValue == widget.value,
canTapOnHeader: true,
backgroundColor: Colors.transparent,
), ),
), ],
body: Column( );
crossAxisAlignment: CrossAxisAlignment.start, },
children: [
const PopupMenuDivider(height: 0),
...widget.items,
const PopupMenuDivider(height: 0),
],
),
isExpanded: _isExpanded,
canTapOnHeader: true,
backgroundColor: Colors.transparent,
),
],
); );
if (!widget.enabled) { if (!widget.enabled) {
child = IgnorePointer(child: child); child = IgnorePointer(child: child);

View file

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
class QueryBar extends StatefulWidget { class QueryBar extends StatefulWidget {
final ValueNotifier<String> queryNotifier; final ValueNotifier<String> queryNotifier;
final FocusNode? focusNode; final FocusNode? focusNode;
final EdgeInsetsGeometry? leadingPadding;
final IconData? icon; final IconData? icon;
final String? hintText; final String? hintText;
final bool editable; final bool editable;
@ -15,6 +16,7 @@ class QueryBar extends StatefulWidget {
super.key, super.key,
required this.queryNotifier, required this.queryNotifier,
this.focusNode, this.focusNode,
this.leadingPadding,
this.icon, this.icon,
this.hintText, this.hintText,
this.editable = true, this.editable = true,
@ -60,7 +62,7 @@ class _QueryBarState extends State<QueryBar> {
focusNode: widget.focusNode ?? FocusNode(), focusNode: widget.focusNode ?? FocusNode(),
decoration: InputDecoration( decoration: InputDecoration(
icon: Padding( icon: Padding(
padding: const EdgeInsetsDirectional.only(start: 16), padding: widget.leadingPadding ?? const EdgeInsetsDirectional.only(start: 16),
child: Icon(widget.icon ?? AIcons.filter), child: Icon(widget.icon ?? AIcons.filter),
), ),
hintText: widget.hintText ?? MaterialLocalizations.of(context).searchFieldLabel, hintText: widget.hintText ?? MaterialLocalizations.of(context).searchFieldLabel,

View file

@ -35,7 +35,7 @@ class ReselectableRadioListTile<T> extends StatelessWidget {
this.selected = false, this.selected = false,
this.controlAffinity = ListTileControlAffinity.platform, this.controlAffinity = ListTileControlAffinity.platform,
this.autofocus = false, this.autofocus = false,
}) : assert(!isThreeLine || subtitle != null); }) : assert(!isThreeLine || subtitle != null);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -206,7 +206,7 @@ class _AvesFloatingBarState extends State<AvesFloatingBar> with RouteAware {
return ValueListenableBuilder<bool>( return ValueListenableBuilder<bool>(
valueListenable: _isBlurAllowedNotifier, valueListenable: _isBlurAllowedNotifier,
builder: (context, isBlurAllowed, child) { builder: (context, isBlurAllowed, child) {
final blurred = isBlurAllowed && context.select<Settings, bool>((s) => s.enableOverlayBlurEffect); final blurred = isBlurAllowed && context.select<Settings, bool>((s) => s.enableBlurEffect);
return Container( return Container(
foregroundDecoration: BoxDecoration( foregroundDecoration: BoxDecoration(
border: Border.all( border: Border.all(

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