Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2024-03-11 23:33:40 +01:00
commit a02131c7c8
89 changed files with 747 additions and 391 deletions

@ -1 +1 @@
Subproject commit abb292a07e20d696c4568099f918f6c5f330e6b0 Subproject commit ba393198430278b6595976de84fe170f553cc728

View file

@ -10,7 +10,7 @@ jobs:
name: Build and release artifacts. name: Build and release artifacts.
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-java@v3 - uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
@ -75,7 +75,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle - name: Upload app bundle
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: appbundle name: appbundle
path: outputs/app-play-release.aab path: outputs/app-play-release.aab
@ -85,15 +85,15 @@ jobs:
needs: [ build ] needs: [ build ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Get appbundle from artifacts. - name: Get appbundle from artifacts.
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: appbundle name: appbundle
- name: Release app to beta channel. - name: Release app to beta channel.
uses: r0adkll/upload-google-play@v1.1.1 uses: r0adkll/upload-google-play@v1.1.3
with: with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }} serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: deckers.thibault.aves packageName: deckers.thibault.aves

View file

@ -4,6 +4,22 @@ 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.10.6"></a>[v1.10.6] - 2024-03-11
### Added
- Cataloguing: detect/filter HDR videos
### Changed
- check Media Store changes when resuming app
- disabling animations also applies to pop up menus
- upgraded Flutter to stable v3.19.3
### Fixed
- engine leak from analysis worker
## <a id="v1.10.5"></a>[v1.10.5] - 2024-02-22 ## <a id="v1.10.5"></a>[v1.10.5] - 2024-02-22
### Added ### Added

View file

@ -44,11 +44,22 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
workCont = cont workCont = cont
onStart() onStart()
} }
dispose()
return Result.success() return Result.success()
} }
private suspend fun dispose() {
Log.i(LOG_TAG, "Clean analysis worker $id")
flutterEngine?.let {
FlutterUtils.runOnUiThread {
it.destroy()
}
flutterEngine = null
}
}
private fun onStart() { private fun onStart() {
Log.i(LOG_TAG, "Start analysis worker") Log.i(LOG_TAG, "Start analysis worker $id")
runBlocking { runBlocking {
FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) { FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
flutterEngine = it flutterEngine = it

View file

@ -175,6 +175,18 @@ open class MainActivity : FlutterFragmentActivity() {
} }
} }
override fun onResume() {
super.onResume()
mediaStoreChangeStreamHandler.onAppResume()
settingsChangeStreamHandler.onAppResume()
}
override fun onPause() {
mediaStoreChangeStreamHandler.onAppPause()
settingsChangeStreamHandler.onAppPause()
super.onPause()
}
override fun onStop() { override fun onStop() {
Log.i(LOG_TAG, "onStop") Log.i(LOG_TAG, "onStop")
super.onStop() super.onStop()

View file

@ -15,8 +15,12 @@ import android.util.Log
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import com.drew.metadata.file.FileTypeDirectory import com.drew.metadata.file.FileTypeDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.* import deckers.thibault.aves.metadata.ExifInterfaceHelper
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper
import deckers.thibault.aves.metadata.Metadata
import deckers.thibault.aves.metadata.Mp4ParserHelper
import deckers.thibault.aves.metadata.Mp4ParserHelper.dumpBoxes import deckers.thibault.aves.metadata.Mp4ParserHelper.dumpBoxes
import deckers.thibault.aves.metadata.PixyMetaHelper
import deckers.thibault.aves.metadata.metadataextractor.Helper import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.LogUtils

View file

@ -12,7 +12,7 @@ import androidx.core.content.pm.ShortcutManagerCompat
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MemoryUtils
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
@ -35,6 +35,8 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass) "getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled) "isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
"requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission) "requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission)
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
"requestGarbageCollection" -> safe(call, result, ::requestGarbageCollection)
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
@ -123,6 +125,15 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
result.success(true) result.success(true)
} }
private fun getAvailableHeapSize(@Suppress("unused_parameter") methodCall: MethodCall, result: MethodChannel.Result) {
result.success(MemoryUtils.getAvailableHeapSize())
}
private fun requestGarbageCollection(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Runtime.getRuntime().gc()
result.success(true)
}
companion object { companion object {
const val CHANNEL = "deckers.thibault/aves/device" const val CHANNEL = "deckers.thibault/aves/device"
} }

View file

@ -3,6 +3,8 @@ package deckers.thibault.aves.channel.calls
import android.content.Context import android.content.Context
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.Uri import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.provider.MediaStoreImageProvider import deckers.thibault.aves.model.provider.MediaStoreImageProvider
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
@ -20,13 +22,15 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
when (call.method) { when (call.method) {
"checkObsoleteContentIds" -> ioScope.launch { safe(call, result, ::checkObsoleteContentIds) } "checkObsoleteContentIds" -> ioScope.launch { safe(call, result, ::checkObsoleteContentIds) }
"checkObsoletePaths" -> ioScope.launch { safe(call, result, ::checkObsoletePaths) } "checkObsoletePaths" -> ioScope.launch { safe(call, result, ::checkObsoletePaths) }
"getChangedUris" -> ioScope.launch { safe(call, result, ::getChangedUris) }
"getGeneration" -> ioScope.launch { safe(call, result, ::getGeneration) }
"scanFile" -> ioScope.launch { safe(call, result, ::scanFile) } "scanFile" -> ioScope.launch { safe(call, result, ::scanFile) }
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
private fun checkObsoleteContentIds(call: MethodCall, result: MethodChannel.Result) { private fun checkObsoleteContentIds(call: MethodCall, result: MethodChannel.Result) {
val knownContentIds = call.argument<List<Int?>>("knownContentIds") val knownContentIds = call.argument<List<Number?>>("knownContentIds")?.map { it?.toLong() }
if (knownContentIds == null) { if (knownContentIds == null) {
result.error("checkObsoleteContentIds-args", "missing arguments", null) result.error("checkObsoleteContentIds-args", "missing arguments", null)
return return
@ -35,7 +39,7 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
} }
private fun checkObsoletePaths(call: MethodCall, result: MethodChannel.Result) { private fun checkObsoletePaths(call: MethodCall, result: MethodChannel.Result) {
val knownPathById = call.argument<Map<Int?, String?>>("knownPathById") val knownPathById = call.argument<Map<Number?, String?>>("knownPathById")?.mapKeys { it.key?.toLong() }
if (knownPathById == null) { if (knownPathById == null) {
result.error("checkObsoletePaths-args", "missing arguments", null) result.error("checkObsoletePaths-args", "missing arguments", null)
return return
@ -43,6 +47,25 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
result.success(MediaStoreImageProvider().checkObsoletePaths(context, knownPathById)) result.success(MediaStoreImageProvider().checkObsoletePaths(context, knownPathById))
} }
private fun getChangedUris(call: MethodCall, result: MethodChannel.Result) {
val sinceGeneration = call.argument<Int>("sinceGeneration")
if (sinceGeneration == null) {
result.error("getChangedUris-args", "missing arguments", null)
return
}
val uris = MediaStoreImageProvider().getChangedUris(context, sinceGeneration)
result.success(uris)
}
private fun getGeneration(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val generation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
MediaStore.getGeneration(context, MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
null
}
result.success(generation)
}
private fun scanFile(call: MethodCall, result: MethodChannel.Result) { private fun scanFile(call: MethodCall, result: MethodChannel.Result) {
val path = call.argument<String>("path") val path = call.argument<String>("path")
val mimeType = call.argument<String>("mimeType") val mimeType = call.argument<String>("mimeType")

View file

@ -1,6 +1,7 @@
package deckers.thibault.aves.channel.calls package deckers.thibault.aves.channel.calls
import android.content.Context import android.content.Context
import android.media.MediaFormat
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@ -559,7 +560,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// identification of embedded gain map // identification of embedded gain map
if (xmpMeta.hasHdrGainMap()) { if (xmpMeta.hasHdrGainMap()) {
flags = flags or MASK_HAS_HDR_GAIN_MAP flags = flags or MASK_IS_HDR
} }
} catch (e: XMPException) { } catch (e: XMPException) {
Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e) Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e)
@ -797,6 +798,14 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
} }
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_COLOR_TRANSFER) {
if (it == MediaFormat.COLOR_TRANSFER_ST2084 || it == MediaFormat.COLOR_TRANSFER_HLG) {
flags = flags or MASK_IS_HDR
}
}
}
metadataMap[KEY_FLAGS] = flags metadataMap[KEY_FLAGS] = flags
} catch (e: Exception) { } catch (e: Exception) {
Log.w(LOG_TAG, "failed to get catalog metadata by MediaMetadataRetriever for uri=$uri", e) Log.w(LOG_TAG, "failed to get catalog metadata by MediaMetadataRetriever for uri=$uri", e)
@ -1300,7 +1309,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private const val MASK_IS_360 = 1 shl 3 private const val MASK_IS_360 = 1 shl 3
private const val MASK_IS_MULTIPAGE = 1 shl 4 private const val MASK_IS_MULTIPAGE = 1 shl 4
private const val MASK_IS_MOTION_PHOTO = 1 shl 5 private const val MASK_IS_MOTION_PHOTO = 1 shl 5
private const val MASK_HAS_HDR_GAIN_MAP = 1 shl 6 private const val MASK_IS_HDR = 1 shl 6 // for images: embedded HDR gainmap, for videos: HDR color transfer
private const val XMP_SUBJECTS_SEPARATOR = ";" private const val XMP_SUBJECTS_SEPARATOR = ";"
// overlay metadata // overlay metadata

View file

@ -135,7 +135,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
} }
val trashDirs = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) } val trashDirs = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }
val trashItemPaths = trashDirs.flatMap { dir -> dir.listFiles()?.map { file -> file.path } ?: listOf() } val trashItemPaths = trashDirs.flatMap { dir -> dir.listFiles()?.mapNotNull { file -> file?.path } ?: listOf() }
val untrackedPaths = trashItemPaths.filterNot(knownPaths::contains).toList() val untrackedPaths = trashItemPaths.filterNot(knownPaths::contains).toList()
result.success(untrackedPaths) result.success(untrackedPaths)
@ -150,7 +150,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
} }
val vaultDir = File(StorageUtils.getVaultRoot(context), vault) val vaultDir = File(StorageUtils.getVaultRoot(context), vault)
val vaultItemPaths = vaultDir.listFiles()?.map { file -> file.path } ?: listOf() val vaultItemPaths = vaultDir.listFiles()?.mapNotNull { file -> file?.path } ?: listOf()
val untrackedPaths = vaultItemPaths.filterNot(knownPaths::contains).toList() val untrackedPaths = vaultItemPaths.filterNot(knownPaths::contains).toList()
result.success(untrackedPaths) result.success(untrackedPaths)

View file

@ -30,12 +30,26 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
} }
init { init {
onAppResume()
}
fun dispose() {
onAppPause()
}
fun onAppResume() {
Log.i(LOG_TAG, "start listening to Media Store")
context.contentResolver.apply { context.contentResolver.apply {
registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver) registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, contentObserver) registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
} }
} }
fun onAppPause() {
Log.i(LOG_TAG, "stop listening to Media Store")
context.contentResolver.unregisterContentObserver(contentObserver)
}
override fun onListen(arguments: Any?, eventSink: EventSink) { override fun onListen(arguments: Any?, eventSink: EventSink) {
this.eventSink = eventSink this.eventSink = eventSink
handler = Handler(Looper.getMainLooper()) handler = Handler(Looper.getMainLooper())
@ -45,10 +59,6 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
Log.i(LOG_TAG, "onCancel arguments=$arguments") Log.i(LOG_TAG, "onCancel arguments=$arguments")
} }
fun dispose() {
context.contentResolver.unregisterContentObserver(contentObserver)
}
private fun success(uri: String?) { private fun success(uri: String?) {
handler?.post { handler?.post {
try { try {

View file

@ -19,13 +19,12 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
private lateinit var eventSink: EventSink private lateinit var eventSink: EventSink
private lateinit var handler: Handler private lateinit var handler: Handler
private var knownEntries: Map<Int?, Int?>? = null private var knownEntries: Map<Long?, Int?>? = null
private var directory: String? = null private var directory: String? = null
init { init {
if (arguments is Map<*, *>) { if (arguments is Map<*, *>) {
@Suppress("unchecked_cast") knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
knownEntries = arguments["knownEntries"] as Map<Int?, Int?>?
directory = arguments["directory"] as String? directory = arguments["directory"] as String?
} }
} }

View file

@ -62,9 +62,21 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
} }
init { init {
context.contentResolver.apply { onAppResume()
registerContentObserver(Settings.System.CONTENT_URI, true, contentObserver)
} }
fun dispose() {
onAppPause()
}
fun onAppResume() {
Log.i(LOG_TAG, "start listening to system settings")
context.contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, contentObserver)
}
fun onAppPause() {
Log.i(LOG_TAG, "stop listening to system settings")
context.contentResolver.unregisterContentObserver(contentObserver)
} }
override fun onListen(arguments: Any?, eventSink: EventSink) { override fun onListen(arguments: Any?, eventSink: EventSink) {
@ -76,10 +88,6 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
Log.i(LOG_TAG, "onCancel arguments=$arguments") Log.i(LOG_TAG, "onCancel arguments=$arguments")
} }
fun dispose() {
context.contentResolver.unregisterContentObserver(contentObserver)
}
private fun success(settings: FieldMap) { private fun success(settings: FieldMap) {
handler?.post { handler?.post {
try { try {

View file

@ -166,7 +166,7 @@ object Helper {
// This seems to cover all known Exif and Xmp date strings // This seems to cover all known Exif and Xmp date strings
// Note that " : : : : " is a valid date string according to the Exif spec (which means 'unknown date'): http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html // Note that " : : : : " is a valid date string according to the Exif spec (which means 'unknown date'): http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html
private val datePatterns = arrayOf( private val dateFormats = arrayOf(
"yyyy:MM:dd HH:mm:ss", "yyyy:MM:dd HH:mm:ss",
"yyyy:MM:dd HH:mm", "yyyy:MM:dd HH:mm",
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss",
@ -179,7 +179,7 @@ object Helper {
"yyyy-MM", "yyyy-MM",
"yyyyMMdd", // as used in IPTC data "yyyyMMdd", // as used in IPTC data
"yyyy" "yyyy"
) ).map { SimpleDateFormat(it, Locale.ROOT) }.toTypedArray()
private val subsecondPattern = Pattern.compile("(\\d\\d:\\d\\d:\\d\\d)(\\.\\d+)") private val subsecondPattern = Pattern.compile("(\\d\\d:\\d\\d:\\d\\d)(\\.\\d+)")
private val timeZonePattern = Pattern.compile("(Z|[+-]\\d\\d:\\d\\d|[+-]\\d\\d\\d\\d)$") private val timeZonePattern = Pattern.compile("(Z|[+-]\\d\\d:\\d\\d|[+-]\\d\\d\\d\\d)$")
private val calendar: Calendar = GregorianCalendar() private val calendar: Calendar = GregorianCalendar()
@ -210,11 +210,10 @@ object Helper {
effectiveTimeZone = TimeZone.getTimeZone("GMT" + timeZoneMatcher.group().replace("Z".toRegex(), "")) effectiveTimeZone = TimeZone.getTimeZone("GMT" + timeZoneMatcher.group().replace("Z".toRegex(), ""))
dateString = timeZoneMatcher.replaceAll("") dateString = timeZoneMatcher.replaceAll("")
} }
for (datePattern in datePatterns) { for (dateFormat in dateFormats) {
try { try {
val parsed = SimpleDateFormat(datePattern, Locale.ROOT).apply { dateFormat.timeZone = effectiveTimeZone ?: TimeZone.getTimeZone("GMT") // don't interpret zone time
this.timeZone = effectiveTimeZone ?: TimeZone.getTimeZone("GMT") // don't interpret zone time val parsed = dateFormat.parse(dateString)
}.parse(dateString)
if (parsed != null) { if (parsed != null) {
calendar.time = parsed calendar.time = parsed
if (calendar.get(Calendar.YEAR) < PARSED_DATE_YEAR_MAX) { if (calendar.get(Calendar.YEAR) < PARSED_DATE_YEAR_MAX) {

View file

@ -3,7 +3,11 @@ package deckers.thibault.aves.model.provider
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.RecoverableSecurityException import android.app.RecoverableSecurityException
import android.content.* import android.content.ContentResolver
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.ContextWrapper
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.Uri import android.net.Uri
@ -35,7 +39,7 @@ import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.io.SyncFailedException import java.io.SyncFailedException
import java.util.* import java.util.Locale
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -45,11 +49,11 @@ import kotlin.coroutines.suspendCoroutine
class MediaStoreImageProvider : ImageProvider() { class MediaStoreImageProvider : ImageProvider() {
fun fetchAll( fun fetchAll(
context: Context, context: Context,
knownEntries: Map<Int?, Int?>, knownEntries: Map<Long?, Int?>,
directory: String?, directory: String?,
handleNewEntry: NewEntryHandler, handleNewEntry: NewEntryHandler,
) { ) {
val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean { val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
val knownDate = knownEntries[contentId] val knownDate = knownEntries[contentId]
return knownDate == null || knownDate < dateModifiedSecs return knownDate == null || knownDate < dateModifiedSecs
} }
@ -89,7 +93,7 @@ class MediaStoreImageProvider : ImageProvider() {
var found = false var found = false
val fetched = arrayListOf<FieldMap>() val fetched = arrayListOf<FieldMap>()
val id = uri.tryParseId() val id = uri.tryParseId()
val alwaysValid: NewEntryChecker = fun(_: Int, _: Int): Boolean = true val alwaysValid: NewEntryChecker = fun(_: Long, _: Int): Boolean = true
val onSuccess: NewEntryHandler = fun(entry: FieldMap) { fetched.add(entry) } val onSuccess: NewEntryHandler = fun(entry: FieldMap) { fetched.add(entry) }
if (id != null) { if (id != null) {
if (sourceMimeType == null || isImage(sourceMimeType)) { if (sourceMimeType == null || isImage(sourceMimeType)) {
@ -119,8 +123,8 @@ class MediaStoreImageProvider : ImageProvider() {
} }
} }
fun checkObsoleteContentIds(context: Context, knownContentIds: List<Int?>): List<Int> { fun checkObsoleteContentIds(context: Context, knownContentIds: List<Long?>): List<Long> {
val foundContentIds = HashSet<Int>() val foundContentIds = HashSet<Long>()
fun check(context: Context, contentUri: Uri) { fun check(context: Context, contentUri: Uri) {
val projection = arrayOf(MediaStore.MediaColumns._ID) val projection = arrayOf(MediaStore.MediaColumns._ID)
try { try {
@ -128,7 +132,7 @@ class MediaStoreImageProvider : ImageProvider() {
if (cursor != null) { if (cursor != null) {
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID) val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
foundContentIds.add(cursor.getInt(idColumn)) foundContentIds.add(cursor.getLong(idColumn))
} }
cursor.close() cursor.close()
} }
@ -141,8 +145,8 @@ class MediaStoreImageProvider : ImageProvider() {
return knownContentIds.subtract(foundContentIds).filterNotNull().toList() return knownContentIds.subtract(foundContentIds).filterNotNull().toList()
} }
fun checkObsoletePaths(context: Context, knownPathById: Map<Int?, String?>): List<Int> { fun checkObsoletePaths(context: Context, knownPathById: Map<Long?, String?>): List<Long> {
val obsoleteIds = ArrayList<Int>() val obsoleteIds = ArrayList<Long>()
fun check(context: Context, contentUri: Uri) { fun check(context: Context, contentUri: Uri) {
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA) val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
try { try {
@ -151,7 +155,7 @@ class MediaStoreImageProvider : ImageProvider() {
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID) val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA) val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = cursor.getInt(idColumn) val id = cursor.getLong(idColumn)
val path = cursor.getString(pathColumn) val path = cursor.getString(pathColumn)
if (knownPathById.containsKey(id) && knownPathById[id] != path) { if (knownPathById.containsKey(id) && knownPathById[id] != path) {
obsoleteIds.add(id) obsoleteIds.add(id)
@ -168,6 +172,31 @@ class MediaStoreImageProvider : ImageProvider() {
return obsoleteIds return obsoleteIds
} }
fun getChangedUris(context: Context, sinceGeneration: Int): List<String> {
val changedUris = ArrayList<String>()
fun check(context: Context, contentUri: Uri) {
val projection = arrayOf(MediaStore.MediaColumns._ID)
val selection = "${MediaStore.MediaColumns.GENERATION_MODIFIED} > ?"
val selectionArgs = arrayOf(sinceGeneration.toString())
try {
val cursor = context.contentResolver.query(contentUri, projection, selection, selectionArgs, null)
if (cursor != null) {
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
changedUris.add(ContentUris.withAppendedId(contentUri, id).toString())
}
cursor.close()
}
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to get content IDs for contentUri=$contentUri", e)
}
}
check(context, IMAGE_CONTENT_URI)
check(context, VIDEO_CONTENT_URI)
return changedUris
}
private fun fetchFrom( private fun fetchFrom(
context: Context, context: Context,
isValidEntry: NewEntryChecker, isValidEntry: NewEntryChecker,
@ -207,12 +236,12 @@ class MediaStoreImageProvider : ImageProvider() {
val needDuration = projection.contentEquals(VIDEO_PROJECTION) val needDuration = projection.contentEquals(VIDEO_PROJECTION)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val contentId = cursor.getInt(idColumn) val id = cursor.getLong(idColumn)
val dateModifiedSecs = cursor.getInt(dateModifiedColumn) val dateModifiedSecs = cursor.getInt(dateModifiedColumn)
if (isValidEntry(contentId, dateModifiedSecs)) { if (isValidEntry(id, dateModifiedSecs)) {
// for multiple items, `contentUri` is the root without ID, // for multiple items, `contentUri` is the root without ID,
// but for single items, `contentUri` already contains the ID // but for single items, `contentUri` already contains the ID
val itemUri = if (contentUriContainsId) contentUri else ContentUris.withAppendedId(contentUri, contentId.toLong()) val itemUri = if (contentUriContainsId) contentUri else ContentUris.withAppendedId(contentUri, id)
// `mimeType` can be registered as null for file media URIs with unsupported media types (e.g. TIFF on old devices) // `mimeType` can be registered as null for file media URIs with unsupported media types (e.g. TIFF on old devices)
// in that case we try to use the MIME type provided along the URI // in that case we try to use the MIME type provided along the URI
val mimeType: String? = cursor.getString(mimeTypeColumn) ?: fileMimeType val mimeType: String? = cursor.getString(mimeTypeColumn) ?: fileMimeType
@ -237,7 +266,7 @@ class MediaStoreImageProvider : ImageProvider() {
"sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null, "sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
"durationMillis" to durationMillis, "durationMillis" to durationMillis,
// only for map export // only for map export
"contentId" to contentId, "contentId" to id,
) )
if (MimeTypes.isHeic(mimeType)) { if (MimeTypes.isHeic(mimeType)) {
@ -930,8 +959,10 @@ class MediaStoreImageProvider : ImageProvider() {
try { try {
val cursor = context.contentResolver.query(contentUri, projection, selection, selectionArgs, null) val cursor = context.contentResolver.query(contentUri, projection, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
cursor.getColumnIndex(MediaStore.MediaColumns._ID).let { val idColumn = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
if (it != -1) mediaContentUri = ContentUris.withAppendedId(contentUri, cursor.getLong(it)) if (idColumn != -1) {
val id = cursor.getLong(idColumn)
mediaContentUri = ContentUris.withAppendedId(contentUri, id)
} }
cursor.close() cursor.close()
} }
@ -994,4 +1025,4 @@ object MediaColumns {
typealias NewEntryHandler = (entry: FieldMap) -> Unit typealias NewEntryHandler = (entry: FieldMap) -> Unit
private typealias NewEntryChecker = (contentId: Int, dateModifiedSecs: Int) -> Boolean private typealias NewEntryChecker = (contentId: Long, dateModifiedSecs: Int) -> Boolean

View file

@ -7,11 +7,13 @@ object MemoryUtils {
fun canAllocate(byteSize: Number?): Boolean { fun canAllocate(byteSize: Number?): Boolean {
byteSize ?: return true byteSize ?: return true
val availableHeapSize = Runtime.getRuntime().let { it.maxMemory() - (it.totalMemory() - it.freeMemory()) } val availableHeapSize = getAvailableHeapSize()
val danger = byteSize.toLong() > availableHeapSize val danger = byteSize.toLong() > availableHeapSize
if (danger) { if (danger) {
Log.e(LOG_TAG, "trying to handle $byteSize bytes, with only $availableHeapSize free bytes") Log.e(LOG_TAG, "trying to handle $byteSize bytes, with only $availableHeapSize free bytes")
} }
return !danger return !danger
} }
fun getAvailableHeapSize() = Runtime.getRuntime().let { it.maxMemory() - (it.totalMemory() - it.freeMemory()) }
} }

View file

@ -8,4 +8,5 @@
<string name="analysis_notification_action_stop">توقف کردن</string> <string name="analysis_notification_action_stop">توقف کردن</string>
<string name="app_widget_label">قاب عکس</string> <string name="app_widget_label">قاب عکس</string>
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="safe_mode_shortcut_short_label">حالت امن</string>
</resources> </resources>

View file

@ -0,0 +1,4 @@
In v1.10.6:
- detect HDR videos (but do not play them in their full HDR glory)
- removing animations also applies to pop up menus
Full changelog available on GitHub

View file

@ -0,0 +1,4 @@
In v1.10.6:
- detect HDR videos (but do not play them in their full HDR glory)
- removing animations also applies to pop up menus
Full changelog available on GitHub

View file

@ -228,7 +228,7 @@
"@filterTypeSphericalVideoLabel": {}, "@filterTypeSphericalVideoLabel": {},
"filterNoTitleLabel": "Без назвы", "filterNoTitleLabel": "Без назвы",
"@filterNoTitleLabel": {}, "@filterNoTitleLabel": {},
"filterOnThisDayLabel": "У гэты дзень", "filterOnThisDayLabel": "Ў гэты дзень",
"@filterOnThisDayLabel": {}, "@filterOnThisDayLabel": {},
"filterRatingRejectedLabel": "Адхілена", "filterRatingRejectedLabel": "Адхілена",
"@filterRatingRejectedLabel": {}, "@filterRatingRejectedLabel": {},
@ -263,7 +263,7 @@
"@nameConflictStrategyReplace": {}, "@nameConflictStrategyReplace": {},
"filterAspectRatioLandscapeLabel": "Ландшафтныя", "filterAspectRatioLandscapeLabel": "Ландшафтныя",
"@filterAspectRatioLandscapeLabel": {}, "@filterAspectRatioLandscapeLabel": {},
"filterBinLabel": "Кошык", "filterBinLabel": "Сметніца",
"@filterBinLabel": {}, "@filterBinLabel": {},
"filterFavouriteLabel": "Выбранае", "filterFavouriteLabel": "Выбранае",
"@filterFavouriteLabel": {}, "@filterFavouriteLabel": {},
@ -551,7 +551,7 @@
"@mapPointNorthUpTooltip": {}, "@mapPointNorthUpTooltip": {},
"viewerInfoLabelCoordinates": "Каардынаты", "viewerInfoLabelCoordinates": "Каардынаты",
"@viewerInfoLabelCoordinates": {}, "@viewerInfoLabelCoordinates": {},
"viewerInfoLabelOwner": "Уладальнік", "viewerInfoLabelOwner": "Ўладальнік",
"@viewerInfoLabelOwner": {}, "@viewerInfoLabelOwner": {},
"viewerInfoLabelDuration": "Працягласць", "viewerInfoLabelDuration": "Працягласць",
"@viewerInfoLabelDuration": {}, "@viewerInfoLabelDuration": {},
@ -625,7 +625,7 @@
"@mapZoomOutTooltip": {}, "@mapZoomOutTooltip": {},
"openMapPageTooltip": "Паглядзець на старонцы карты", "openMapPageTooltip": "Паглядзець на старонцы карты",
"@openMapPageTooltip": {}, "@openMapPageTooltip": {},
"mapEmptyRegion": "У гэтым рэгіёне няма малюнкаў", "mapEmptyRegion": "Ў гэтым рэгіёне няма малюнкаў",
"@mapEmptyRegion": {}, "@mapEmptyRegion": {},
"viewerInfoSearchEmpty": "Няма адпаведных ключоў", "viewerInfoSearchEmpty": "Няма адпаведных ключоў",
"@viewerInfoSearchEmpty": {}, "@viewerInfoSearchEmpty": {},
@ -683,7 +683,7 @@
"@setCoverDialogCustom": {}, "@setCoverDialogCustom": {},
"aboutBugCopyInfoInstruction": "Скапіяваць сістэмную інфармацыю", "aboutBugCopyInfoInstruction": "Скапіяваць сістэмную інфармацыю",
"@aboutBugCopyInfoInstruction": {}, "@aboutBugCopyInfoInstruction": {},
"vaultBinUsageDialogMessage": "Некаторыя сховішчы выкарыстоўваюць кошык.", "vaultBinUsageDialogMessage": "Некаторыя сховішчы выкарыстоўваюць сметніцу.",
"@vaultBinUsageDialogMessage": {}, "@vaultBinUsageDialogMessage": {},
"aboutBugSaveLogInstruction": "Захаваць журналы праграмы ў файл", "aboutBugSaveLogInstruction": "Захаваць журналы праграмы ў файл",
"@aboutBugSaveLogInstruction": {}, "@aboutBugSaveLogInstruction": {},
@ -707,7 +707,7 @@
"@aboutBugSectionTitle": {}, "@aboutBugSectionTitle": {},
"aboutBugCopyInfoButton": "Скапіяваць", "aboutBugCopyInfoButton": "Скапіяваць",
"@aboutBugCopyInfoButton": {}, "@aboutBugCopyInfoButton": {},
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент у кошык?} few{Перамясціць гэтыя {count} элемента у кошык?} other{Перамясціць гэтыя {count} элементаў у кошык?}}", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент ў сметніцу?} few{Перамясціць гэтыя {count} элемента ў сметніцу?} other{Перамясціць гэтыя {count} элементаў ў сметніцу?}}",
"@binEntriesConfirmationDialogMessage": { "@binEntriesConfirmationDialogMessage": {
"placeholders": { "placeholders": {
"count": {} "count": {}
@ -937,7 +937,7 @@
"@settingsActionImportDialogTitle": {}, "@settingsActionImportDialogTitle": {},
"albumGroupTier": "Па ўзроўні", "albumGroupTier": "Па ўзроўні",
"@albumGroupTier": {}, "@albumGroupTier": {},
"drawerCollectionAll": "Уся калекцыя", "drawerCollectionAll": "Ўся калекцыя",
"@drawerCollectionAll": {}, "@drawerCollectionAll": {},
"sortByItemCount": "Па колькасці элементаў", "sortByItemCount": "Па колькасці элементаў",
"@sortByItemCount": {}, "@sortByItemCount": {},
@ -985,7 +985,7 @@
"@searchMetadataSectionTitle": {}, "@searchMetadataSectionTitle": {},
"settingsShowBottomNavigationBar": "Паказаць ніжнюю панэль навігацыі", "settingsShowBottomNavigationBar": "Паказаць ніжнюю панэль навігацыі",
"@settingsShowBottomNavigationBar": {}, "@settingsShowBottomNavigationBar": {},
"collectionActionEmptyBin": "Ачысціць кошык", "collectionActionEmptyBin": "Ачысціць сметніцу",
"@collectionActionEmptyBin": {}, "@collectionActionEmptyBin": {},
"sortOrderAtoZ": "Ад А да Я", "sortOrderAtoZ": "Ад А да Я",
"@sortOrderAtoZ": {}, "@sortOrderAtoZ": {},
@ -1127,7 +1127,7 @@
"@viewDialogLayoutSectionTitle": {}, "@viewDialogLayoutSectionTitle": {},
"searchStatesSectionTitle": "Штаты", "searchStatesSectionTitle": "Штаты",
"@searchStatesSectionTitle": {}, "@searchStatesSectionTitle": {},
"dateThisMonth": "У гэтым месяцы", "dateThisMonth": "Ў гэтым месяцы",
"@dateThisMonth": {}, "@dateThisMonth": {},
"aboutPageTitle": "Пра нас", "aboutPageTitle": "Пра нас",
"@aboutPageTitle": {}, "@aboutPageTitle": {},
@ -1165,7 +1165,7 @@
"@sortOrderHighestFirst": {}, "@sortOrderHighestFirst": {},
"aboutLicensesAndroidLibrariesSectionTitle": "Бібліятэкі Android", "aboutLicensesAndroidLibrariesSectionTitle": "Бібліятэкі Android",
"@aboutLicensesAndroidLibrariesSectionTitle": {}, "@aboutLicensesAndroidLibrariesSectionTitle": {},
"binPageTitle": "Кошык", "binPageTitle": "Сметніца",
"@binPageTitle": {}, "@binPageTitle": {},
"sortByAlbumFileName": "Па назве альбома і файла", "sortByAlbumFileName": "Па назве альбома і файла",
"@sortByAlbumFileName": {}, "@sortByAlbumFileName": {},
@ -1183,9 +1183,9 @@
"@settingsConfirmationBeforeDeleteItems": {}, "@settingsConfirmationBeforeDeleteItems": {},
"settingsConfirmationBeforeMoveUndatedItems": "Спытаць, перш чым перамяшчаць прадметы без даты", "settingsConfirmationBeforeMoveUndatedItems": "Спытаць, перш чым перамяшчаць прадметы без даты",
"@settingsConfirmationBeforeMoveUndatedItems": {}, "@settingsConfirmationBeforeMoveUndatedItems": {},
"settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў у кошык", "settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў ў сметніцу",
"@settingsConfirmationAfterMoveToBinItems": {}, "@settingsConfirmationAfterMoveToBinItems": {},
"settingsConfirmationBeforeMoveToBinItems": "Спытаць, перш чым перамяшчаць элементы ў кошык", "settingsConfirmationBeforeMoveToBinItems": "Спытаць перад тым, як пераносіць элементы ў сметніцу",
"@settingsConfirmationBeforeMoveToBinItems": {}, "@settingsConfirmationBeforeMoveToBinItems": {},
"settingsNavigationDrawerAddAlbum": "Дадаць альбом", "settingsNavigationDrawerAddAlbum": "Дадаць альбом",
"@settingsNavigationDrawerAddAlbum": {}, "@settingsNavigationDrawerAddAlbum": {},
@ -1289,7 +1289,7 @@
"@settingsVideoGestureDoubleTapTogglePlay": {}, "@settingsVideoGestureDoubleTapTogglePlay": {},
"addPathTooltip": "Дадаць шлях", "addPathTooltip": "Дадаць шлях",
"@addPathTooltip": {}, "@addPathTooltip": {},
"settingsEnableBin": "Выкарыстоўваць кошык", "settingsEnableBin": "Выкарыстоўваць сметніцу",
"@settingsEnableBin": {}, "@settingsEnableBin": {},
"collectionMoveSuccessFeedback": "{count, plural, =1{Перамяшчаны 1 элемент} few{Перамяшчаны {count} элементы} other{Перамяшчаны {count} элементаў}}", "collectionMoveSuccessFeedback": "{count, plural, =1{Перамяшчаны 1 элемент} few{Перамяшчаны {count} элементы} other{Перамяшчаны {count} элементаў}}",
"@collectionMoveSuccessFeedback": { "@collectionMoveSuccessFeedback": {
@ -1377,7 +1377,7 @@
"@settingsViewerShowDescription": {}, "@settingsViewerShowDescription": {},
"settingsViewerQuickActionEditorBanner": "Націсніце і ўтрымлівайце, каб перамяшчаць кнопкі і выбіраць дзеянні для адлюстравання ў праглядніку.", "settingsViewerQuickActionEditorBanner": "Націсніце і ўтрымлівайце, каб перамяшчаць кнопкі і выбіраць дзеянні для адлюстравання ў праглядніку.",
"@settingsViewerQuickActionEditorBanner": {}, "@settingsViewerQuickActionEditorBanner": {},
"settingsDisablingBinWarningDialogMessage": "Элементы ў кошыку будуць выдалены назаўжды.", "settingsDisablingBinWarningDialogMessage": "Элементы ў сметніцы будуць выдалены назаўсёды.",
"@settingsDisablingBinWarningDialogMessage": {}, "@settingsDisablingBinWarningDialogMessage": {},
"settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Адлюстраваныя кнопкі", "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Адлюстраваныя кнопкі",
"@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {}, "@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
@ -1521,7 +1521,7 @@
}, },
"collectionActionSetHome": "Ўсталяваць як галоўную", "collectionActionSetHome": "Ўсталяваць як галоўную",
"@collectionActionSetHome": {}, "@collectionActionSetHome": {},
"setHomeCustomCollection": "Уласная калекцыя", "setHomeCustomCollection": "Ўласная калекцыя",
"@setHomeCustomCollection": {}, "@setHomeCustomCollection": {},
"settingsThumbnailShowHdrIcon": "Паказаць значок HDR", "settingsThumbnailShowHdrIcon": "Паказаць значок HDR",
"@settingsThumbnailShowHdrIcon": {} "@settingsThumbnailShowHdrIcon": {}

View file

@ -1354,5 +1354,17 @@
"aboutDataUsageClearCache": "Cache leeren", "aboutDataUsageClearCache": "Cache leeren",
"@aboutDataUsageClearCache": {}, "@aboutDataUsageClearCache": {},
"settingsViewerShowHistogram": "Histogramm anzeigen", "settingsViewerShowHistogram": "Histogramm anzeigen",
"@settingsViewerShowHistogram": {} "@settingsViewerShowHistogram": {},
"overlayHistogramNone": "Keines",
"@overlayHistogramNone": {},
"collectionActionSetHome": "Als Startseite setzen",
"@collectionActionSetHome": {},
"setHomeCustomCollection": "Benutzerdefinierte Sammlung",
"@setHomeCustomCollection": {},
"settingsThumbnailShowHdrIcon": "HDR-Symbol anzeigen",
"@settingsThumbnailShowHdrIcon": {},
"entryActionCast": "Übertragen",
"@entryActionCast": {},
"castDialogTitle": "Geräte zur Übertragung",
"@castDialogTitle": {}
} }

View file

@ -85,7 +85,7 @@
"@chipActionGoToAlbumPage": {}, "@chipActionGoToAlbumPage": {},
"filterNoTagLabel": "بدون برچسب", "filterNoTagLabel": "بدون برچسب",
"@filterNoTagLabel": {}, "@filterNoTagLabel": {},
"appName": "اِیوْز", "appName": "اِیوز",
"@appName": {}, "@appName": {},
"entryActionRestore": "برگرداندن", "entryActionRestore": "برگرداندن",
"@entryActionRestore": {}, "@entryActionRestore": {},
@ -544,5 +544,41 @@
"wallpaperTargetHomeLock": "صفحهٔ خانه و صفحهٔ قفل", "wallpaperTargetHomeLock": "صفحهٔ خانه و صفحهٔ قفل",
"@wallpaperTargetHomeLock": {}, "@wallpaperTargetHomeLock": {},
"viewDialogSortSectionTitle": "ترتیب بندی", "viewDialogSortSectionTitle": "ترتیب بندی",
"@viewDialogSortSectionTitle": {} "@viewDialogSortSectionTitle": {},
"filterRatingRejectedLabel": "رد شده",
"@filterRatingRejectedLabel": {},
"filterLocatedLabel": "واقع شده",
"@filterLocatedLabel": {},
"setCoverDialogAuto": "خودکار",
"@setCoverDialogAuto": {},
"albumTierSpecial": "مشترک",
"@albumTierSpecial": {},
"viewerTransitionParallax": "انطباق",
"@viewerTransitionParallax": {},
"coordinateFormatDecimal": "درجه های اعشاری",
"@coordinateFormatDecimal": {},
"videoResumeButtonLabel": "ادامه",
"@videoResumeButtonLabel": {},
"addShortcutDialogLabel": "عنوان میانبر",
"@addShortcutDialogLabel": {},
"overlayHistogramNone": "هیچکدام",
"@overlayHistogramNone": {},
"overlayHistogramLuminance": "درخشش",
"@overlayHistogramLuminance": {},
"videoPlaybackSkip": "رد کردن",
"@videoPlaybackSkip": {},
"addShortcutButtonLabel": "­",
"@addShortcutButtonLabel": {},
"setCoverDialogCustom": "شخصی",
"@setCoverDialogCustom": {},
"nameConflictDialogSingleSourceMessage": "برخی از پرونده های موجود در پوشه مقصد به همین نام هستند.",
"@nameConflictDialogSingleSourceMessage": {},
"missingSystemFilePickerDialogMessage": "انتخابگر پرونده سیستم وجود ندارد یا غیرفعال است. لطفا آن را فعال کنید و دوباره امتحان کنید",
"@missingSystemFilePickerDialogMessage": {},
"nameConflictDialogMultipleSourceMessage": "برخی پرونده ها نام های یکسانی دارند",
"@nameConflictDialogMultipleSourceMessage": {},
"noMatchingAppDialogMessage": "هیچ کاره ای وجود ندارد که بتواند این موضوع را مدیریت کند.",
"@noMatchingAppDialogMessage": {},
"filterTaggedLabel": "نشان شده",
"@filterTaggedLabel": {}
} }

View file

@ -1356,5 +1356,15 @@
"settingsViewerShowHistogram": "Tampilkan histogram", "settingsViewerShowHistogram": "Tampilkan histogram",
"@settingsViewerShowHistogram": {}, "@settingsViewerShowHistogram": {},
"aboutDataUsageClearCache": "Bersihkan cache", "aboutDataUsageClearCache": "Bersihkan cache",
"@aboutDataUsageClearCache": {} "@aboutDataUsageClearCache": {},
"entryActionCast": "Siarkan",
"@entryActionCast": {},
"setHomeCustomCollection": "Koleksi kustom",
"@setHomeCustomCollection": {},
"collectionActionSetHome": "Tetapkan sebagai beranda",
"@collectionActionSetHome": {},
"settingsThumbnailShowHdrIcon": "Tampilkan ikon HDR",
"@settingsThumbnailShowHdrIcon": {},
"castDialogTitle": "Siarkan Perangkat",
"@castDialogTitle": {}
} }

View file

@ -1232,5 +1232,19 @@
"lengthUnitPixel": "px", "lengthUnitPixel": "px",
"@lengthUnitPixel": {}, "@lengthUnitPixel": {},
"lengthUnitPercent": "%", "lengthUnitPercent": "%",
"@lengthUnitPercent": {} "@lengthUnitPercent": {},
"saveCopyButtonLabel": "コピーを保存",
"@saveCopyButtonLabel": {},
"columnCount": "{count, plural, =1{1 列} other{{count} 列}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"chipActionShowCountryStates": "地域を表示",
"@chipActionShowCountryStates": {},
"entryActionCast": "キャスト",
"@entryActionCast": {},
"editorTransformRotate": "回転",
"@editorTransformRotate": {}
} }

View file

@ -1492,5 +1492,37 @@
"settingsAccessibilityShowPinchGestureAlternatives": "顯示多點觸控手勢的備選方案", "settingsAccessibilityShowPinchGestureAlternatives": "顯示多點觸控手勢的備選方案",
"@settingsAccessibilityShowPinchGestureAlternatives": {}, "@settingsAccessibilityShowPinchGestureAlternatives": {},
"overlayHistogramRGB": "RGB", "overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {} "@overlayHistogramRGB": {},
"settingsThumbnailShowHdrIcon": "顯示 HDR 圖示",
"@settingsThumbnailShowHdrIcon": {},
"aboutDataUsageSectionTitle": "資料用量",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "資料",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "快取記憶體",
"@aboutDataUsageCache": {},
"aboutDataUsageDatabase": "資料庫",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "雜項",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "內部儲存",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "外部儲存",
"@aboutDataUsageExternal": {},
"overlayHistogramNone": "無",
"@overlayHistogramNone": {},
"overlayHistogramLuminance": "亮度",
"@overlayHistogramLuminance": {},
"collectionActionSetHome": "設為首頁",
"@collectionActionSetHome": {},
"setHomeCustomCollection": "自訂收藏品",
"@setHomeCustomCollection": {},
"aboutDataUsageClearCache": "清除快取",
"@aboutDataUsageClearCache": {},
"settingsViewerShowHistogram": "顯示直方圖",
"@settingsViewerShowHistogram": {},
"entryActionCast": "投放",
"@entryActionCast": {},
"castDialogTitle": "投放裝置",
"@castDialogTitle": {}
} }

View file

@ -78,6 +78,7 @@ class Contributors {
Contributor('minh', 'teaminh@skiff.com'), Contributor('minh', 'teaminh@skiff.com'),
Contributor('luckris25', 'lk1thebestl@gmail.com'), Contributor('luckris25', 'lk1thebestl@gmail.com'),
Contributor('Marc Amorós', 'marquitus99@gmail.com'), Contributor('Marc Amorós', 'marquitus99@gmail.com'),
Contributor('elea11', 'p.manuel.warnecke@gmail.com'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali // Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese // Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese // Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
@ -94,6 +95,7 @@ class Contributors {
// Contributor('امیر جهانگرد', 'ijahangard.a@gmail.com'), // Persian // Contributor('امیر جهانگرد', 'ijahangard.a@gmail.com'), // Persian
// Contributor('slasb37', 'p84haghi@gmail.com'), // Persian // Contributor('slasb37', 'p84haghi@gmail.com'), // Persian
// Contributor('mimvahedi', 'vahedi0vahedi@gmail.com'), // Persian // Contributor('mimvahedi', 'vahedi0vahedi@gmail.com'), // Persian
// Contributor('Alireza Rashidi', 'alirezarashidigoorabi@gmail.com'), // Persian
// Contributor('Prasanta-Hembram', 'Prasantahembram720@gmail.com'), // Santali // Contributor('Prasanta-Hembram', 'Prasantahembram720@gmail.com'), // Santali
// Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian // Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian
// Contributor('Shift18', 'bribable.lawyer@posteo.net'), // Swedish // Contributor('Shift18', 'bribable.lawyer@posteo.net'), // Swedish

View file

@ -242,6 +242,11 @@ class AvesEntry with AvesEntryBase {
return _bestDate; return _bestDate;
} }
@override
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
bool get isHdr => _catalogMetadata?.isHdr ?? false;
int get rating => _catalogMetadata?.rating ?? 0; int get rating => _catalogMetadata?.rating ?? 0;
@override @override
@ -303,9 +308,6 @@ class AvesEntry with AvesEntryBase {
return d == null ? null : DateTime(d.year, d.month, d.day); return d == null ? null : DateTime(d.year, d.month, d.day);
} }
@override
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
@override @override
int? get durationMillis => _durationMillis; int? get durationMillis => _durationMillis;

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/geotiff.dart'; import 'package:aves/model/geotiff.dart';
@ -6,10 +8,14 @@ import 'package:aves/model/video/metadata.dart';
import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart'; import 'package:aves/services/metadata/svg_metadata_service.dart';
import 'package:flutter/foundation.dart';
extension ExtraAvesEntryCatalog on AvesEntry { extension ExtraAvesEntryCatalog on AvesEntry {
Future<void> catalog({required bool background, required bool force, required bool persist}) async { Future<void> catalog({required bool background, required bool force, required bool persist}) async {
if (isCatalogued && !force) return; if (isCatalogued && !force) return;
final beforeAvailableHeapSize = await deviceService.getAvailableHeapSize();
if (isSvg) { if (isSvg) {
// vector image sizing is not essential, so we should not spend time for it during loading // vector image sizing is not essential, so we should not spend time for it during loading
// but it is useful anyway (for aspect ratios etc.) so we size them during cataloguing // but it is useful anyway (for aspect ratios etc.) so we size them during cataloguing
@ -53,5 +59,14 @@ extension ExtraAvesEntryCatalog on AvesEntry {
} }
} }
} }
final afterAvailableHeapSize = await deviceService.getAvailableHeapSize();
final diff = beforeAvailableHeapSize - afterAvailableHeapSize;
const largeHeapUsageThreshold = 15 * (1 << 20); // MB
if (diff > largeHeapUsageThreshold) {
debugPrint('Large heap usage (${diff}B) from cataloguing entry=$this size=$sizeBytes');
await deviceService.requestGarbageCollection();
}
} }
} }

View file

@ -13,8 +13,6 @@ extension ExtraAvesEntryMultipage on AvesEntry {
bool get isMotionPhoto => catalogMetadata?.isMotionPhoto ?? false; bool get isMotionPhoto => catalogMetadata?.isMotionPhoto ?? false;
bool get isHdr => catalogMetadata?.hasHdrGainMap ?? false;
String? getBurstKey(List<String> patterns) { String? getBurstKey(List<String> patterns) {
final key = BurstPatterns.getKeyForName(filenameWithoutExtension, patterns); final key = BurstPatterns.getKeyForName(filenameWithoutExtension, patterns);
return key != null ? '$directory/$key' : null; return key != null ? '$directory/$key' : null;

View file

@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
class CatalogMetadata { class CatalogMetadata {
final int id; final int id;
final int? dateMillis; final int? dateMillis;
final bool isAnimated, isGeotiff, is360, isMultiPage, isMotionPhoto, hasHdrGainMap; final bool isAnimated, isGeotiff, is360, isMultiPage, isMotionPhoto, isHdr;
bool isFlipped; bool isFlipped;
int? rotationDegrees; int? rotationDegrees;
final String? mimeType, xmpSubjects, xmpTitle; final String? mimeType, xmpSubjects, xmpTitle;
@ -21,7 +21,7 @@ class CatalogMetadata {
static const _is360Mask = 1 << 3; static const _is360Mask = 1 << 3;
static const _isMultiPageMask = 1 << 4; static const _isMultiPageMask = 1 << 4;
static const _isMotionPhotoMask = 1 << 5; static const _isMotionPhotoMask = 1 << 5;
static const _hasHdrGainMapMask = 1 << 6; static const _isHdr = 1 << 6; // for images: embedded HDR gainmap, for videos: HDR color transfer
CatalogMetadata({ CatalogMetadata({
required this.id, required this.id,
@ -33,7 +33,7 @@ class CatalogMetadata {
this.is360 = false, this.is360 = false,
this.isMultiPage = false, this.isMultiPage = false,
this.isMotionPhoto = false, this.isMotionPhoto = false,
this.hasHdrGainMap = false, this.isHdr = false,
this.rotationDegrees, this.rotationDegrees,
this.xmpSubjects, this.xmpSubjects,
this.xmpTitle, this.xmpTitle,
@ -75,7 +75,7 @@ class CatalogMetadata {
is360: is360, is360: is360,
isMultiPage: isMultiPage ?? this.isMultiPage, isMultiPage: isMultiPage ?? this.isMultiPage,
isMotionPhoto: isMotionPhoto, isMotionPhoto: isMotionPhoto,
hasHdrGainMap: hasHdrGainMap, isHdr: isHdr,
rotationDegrees: rotationDegrees ?? this.rotationDegrees, rotationDegrees: rotationDegrees ?? this.rotationDegrees,
xmpSubjects: xmpSubjects, xmpSubjects: xmpSubjects,
xmpTitle: xmpTitle, xmpTitle: xmpTitle,
@ -97,7 +97,7 @@ class CatalogMetadata {
is360: flags & _is360Mask != 0, is360: flags & _is360Mask != 0,
isMultiPage: flags & _isMultiPageMask != 0, isMultiPage: flags & _isMultiPageMask != 0,
isMotionPhoto: flags & _isMotionPhotoMask != 0, isMotionPhoto: flags & _isMotionPhotoMask != 0,
hasHdrGainMap: flags & _hasHdrGainMapMask != 0, isHdr: flags & _isHdr != 0,
// `rotationDegrees` should default to `sourceRotationDegrees`, not 0 // `rotationDegrees` should default to `sourceRotationDegrees`, not 0
rotationDegrees: map['rotationDegrees'], rotationDegrees: map['rotationDegrees'],
xmpSubjects: map['xmpSubjects'] ?? '', xmpSubjects: map['xmpSubjects'] ?? '',
@ -112,7 +112,7 @@ class CatalogMetadata {
'id': id, 'id': id,
'mimeType': mimeType, 'mimeType': mimeType,
'dateMillis': dateMillis, 'dateMillis': dateMillis,
'flags': (isAnimated ? _isAnimatedMask : 0) | (isFlipped ? _isFlippedMask : 0) | (isGeotiff ? _isGeotiffMask : 0) | (is360 ? _is360Mask : 0) | (isMultiPage ? _isMultiPageMask : 0) | (isMotionPhoto ? _isMotionPhotoMask : 0) | (hasHdrGainMap ? _hasHdrGainMapMask : 0), 'flags': (isAnimated ? _isAnimatedMask : 0) | (isFlipped ? _isFlippedMask : 0) | (isGeotiff ? _isGeotiffMask : 0) | (is360 ? _is360Mask : 0) | (isMultiPage ? _isMultiPageMask : 0) | (isMotionPhoto ? _isMotionPhotoMask : 0) | (isHdr ? _isHdr : 0),
'rotationDegrees': rotationDegrees, 'rotationDegrees': rotationDegrees,
'xmpSubjects': xmpSubjects, 'xmpSubjects': xmpSubjects,
'xmpTitle': xmpTitle, 'xmpTitle': xmpTitle,

View file

@ -1,5 +1,7 @@
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/widgets.dart';
extension ExtraAccessibilityAnimations on AccessibilityAnimations { extension ExtraAccessibilityAnimations on AccessibilityAnimations {
bool get animate { bool get animate {
@ -14,4 +16,17 @@ extension ExtraAccessibilityAnimations on AccessibilityAnimations {
return true; return true;
} }
} }
Duration get popUpAnimationDuration => animate ? ADurations.popupMenuAnimation : Duration.zero;
Duration get popUpAnimationDelay => popUpAnimationDuration + const Duration(milliseconds: ADurations.transitionMarginMillis);
AnimationStyle get popUpAnimationStyle {
return animate
? AnimationStyle(
curve: Curves.easeInOutCubic,
duration: popUpAnimationDuration,
)
: AnimationStyle.noAnimation;
}
} }

View file

@ -449,6 +449,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
} }
if (dataTypes.contains(EntryDataType.catalog)) { if (dataTypes.contains(EntryDataType.catalog)) {
// explicit GC before cataloguing multiple items
await deviceService.requestGarbageCollection();
await Future.forEach(entries, (entry) async { await Future.forEach(entries, (entry) async {
await entry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist); await entry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist);
await metadataDb.updateCatalogMetadata(entry.id, entry.catalogMetadata); await metadataDb.updateCatalogMetadata(entry.id, entry.catalogMetadata);
@ -499,6 +501,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
entryIds: entries?.map((entry) => entry.id).toList(), entryIds: entries?.map((entry) => entry.id).toList(),
); );
} else { } else {
// explicit GC before cataloguing multiple items
await deviceService.requestGarbageCollection();
await catalogEntries(_analysisController, todoEntries); await catalogEntries(_analysisController, todoEntries);
updateDerivedFilters(todoEntries); updateDerivedFilters(todoEntries);
await locateEntries(_analysisController, todoEntries); await locateEntries(_analysisController, todoEntries);

View file

@ -11,12 +11,17 @@ import 'package:aves/model/source/analysis_controller.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/model/vaults/vaults.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/debouncer.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class MediaStoreSource extends CollectionSource { class MediaStoreSource extends CollectionSource {
final Debouncer _changeDebouncer = Debouncer(delay: ADurations.mediaContentChangeDebounceDelay);
final Set<String> _changedUris = {};
int? _lastGeneration;
SourceInitializationState _initState = SourceInitializationState.none; SourceInitializationState _initState = SourceInitializationState.none;
@override @override
@ -36,6 +41,7 @@ class MediaStoreSource extends CollectionSource {
_initState = directory != null ? SourceInitializationState.directory : SourceInitializationState.full; _initState = directory != null ? SourceInitializationState.directory : SourceInitializationState.full;
} }
addDirectories(albums: settings.pinnedFilters.whereType<AlbumFilter>().map((v) => v.album).toSet()); addDirectories(albums: settings.pinnedFilters.whereType<AlbumFilter>().map((v) => v.album).toSet());
await updateGeneration();
unawaited(_loadEntries( unawaited(_loadEntries(
analysisController: analysisController, analysisController: analysisController,
directory: directory, directory: directory,
@ -305,6 +311,34 @@ class MediaStoreSource extends CollectionSource {
return tempUris; return tempUris;
} }
void onStoreChanged(String? uri) {
if (uri != null) _changedUris.add(uri);
if (_changedUris.isNotEmpty) {
_changeDebouncer(() async {
final todo = _changedUris.toSet();
_changedUris.clear();
final tempUris = await refreshUris(todo);
if (tempUris.isNotEmpty) {
_changedUris.addAll(tempUris);
onStoreChanged(null);
}
});
}
}
Future<void> checkForChanges() async {
final sinceGeneration = _lastGeneration;
if (sinceGeneration != null) {
_changedUris.addAll(await mediaStoreService.getChangedUris(sinceGeneration));
onStoreChanged(null);
}
await updateGeneration();
}
Future<void> updateGeneration() async {
_lastGeneration = await mediaStoreService.getGeneration();
}
// vault // vault
Future<void> _loadVaultEntries(String? directory) async { Future<void> _loadVaultEntries(String? directory) async {

View file

@ -17,6 +17,10 @@ abstract class DeviceService {
Future<bool> isSystemFilePickerEnabled(); Future<bool> isSystemFilePickerEnabled();
Future<void> requestMediaManagePermission(); Future<void> requestMediaManagePermission();
Future<int> getAvailableHeapSize();
Future<void> requestGarbageCollection();
} }
class PlatformDeviceService implements DeviceService { class PlatformDeviceService implements DeviceService {
@ -104,4 +108,24 @@ class PlatformDeviceService implements DeviceService {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
} }
@override
Future<int> getAvailableHeapSize() async {
try {
final result = await _platform.invokeMethod('getAvailableHeapSize');
if (result != null) return result as int;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return 0;
}
@override
Future<void> requestGarbageCollection() async {
try {
await _platform.invokeMethod('requestGarbageCollection');
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
}
} }

View file

@ -10,6 +10,10 @@ abstract class MediaStoreService {
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById); Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById);
Future<List<String>> getChangedUris(int sinceGeneration);
Future<int?> getGeneration();
// knownEntries: map of contentId -> dateModifiedSecs // knownEntries: map of contentId -> dateModifiedSecs
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}); Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory});
@ -47,6 +51,29 @@ class PlatformMediaStoreService implements MediaStoreService {
return []; return [];
} }
@override
Future<List<String>> getChangedUris(int sinceGeneration) async {
try {
final result = await _platform.invokeMethod('getChangedUris', <String, dynamic>{
'sinceGeneration': sinceGeneration,
});
return (result as List).cast<String>();
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return [];
}
@override
Future<int?> getGeneration() async {
try {
return await _platform.invokeMethod('getGeneration');
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return null;
}
@override @override
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) { Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
try { try {

View file

@ -1,6 +1,7 @@
import 'package:aves/image_providers/app_icon_image_provider.dart'; import 'package:aves/image_providers/app_icon_image_provider.dart';
import 'package:aves/model/covers.dart'; import 'package:aves/model/covers.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -32,7 +33,7 @@ class AvesColorsProvider extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ProxyProvider<Settings, AvesColorsData>( return ProxyProvider<Settings, AvesColorsData>(
update: (context, settings, __) { update: (context, settings, __) {
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).isDark;
var mode = settings.themeColorMode; var mode = settings.themeColorMode;
if (!allowMonochrome && mode == AvesThemeColorMode.monochrome) { if (!allowMonochrome && mode == AvesThemeColorMode.monochrome) {
mode = AvesThemeColorMode.polychrome; mode = AvesThemeColorMode.polychrome;

View file

@ -2,12 +2,13 @@ import 'package:flutter/foundation.dart';
class ADurations { class ADurations {
// Flutter animations (with margin) // Flutter animations (with margin)
static const popupMenuAnimation = Duration(milliseconds: 300 + 20); // ref `_kMenuDuration` used in `_PopupMenuRoute` static const transitionMarginMillis = 20;
// page transition duration also available via `ModalRoute.of(context)!.transitionDuration * timeDilation` // page transition duration also available via `ModalRoute.of(context)!.transitionDuration * timeDilation`
static const pageTransitionAnimation = Duration(milliseconds: 300 + 20); // ref `transitionDuration` used in `MaterialRouteTransitionMixin` static const pageTransitionAnimation = Duration(milliseconds: 300 + transitionMarginMillis); // ref `transitionDuration` used in `MaterialRouteTransitionMixin`
static const dialogTransitionAnimation = Duration(milliseconds: 150 + 20); // ref `transitionDuration` used in `DialogRoute` static const dialogTransitionAnimation = Duration(milliseconds: 150 + transitionMarginMillis); // ref `transitionDuration` used in `DialogRoute`
static const drawerTransitionAnimation = Duration(milliseconds: 246 + 20); // ref `_kBaseSettleDuration` used in `DrawerControllerState` static const drawerTransitionAnimation = Duration(milliseconds: 246 + transitionMarginMillis); // ref `_kBaseSettleDuration` used in `DrawerControllerState`
static const toggleableTransitionAnimation = Duration(milliseconds: 200 + 20); // ref `_kToggleDuration` used in `ToggleableStateMixin` static const toggleableTransitionAnimation = Duration(milliseconds: 200 + transitionMarginMillis); // ref `_kToggleDuration` used in `ToggleableStateMixin`
// common animations // common animations
static const sweeperOpacityAnimation = Duration(milliseconds: 150); static const sweeperOpacityAnimation = Duration(milliseconds: 150);
@ -16,6 +17,7 @@ class ADurations {
static const appBarTitleAnimation = Duration(milliseconds: 300); static const appBarTitleAnimation = Duration(milliseconds: 300);
static const appBarActionChangeAnimation = Duration(milliseconds: 200); static const appBarActionChangeAnimation = Duration(milliseconds: 200);
static const popupMenuAnimation = Duration(milliseconds: 300);
// filter grids animations // filter grids animations
static const chipDecorationAnimation = Duration(milliseconds: 200); static const chipDecorationAnimation = Duration(milliseconds: 200);

View file

@ -19,11 +19,9 @@ 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/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/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/theme/styles.dart'; import 'package:aves/theme/styles.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/utils/debouncer.dart';
import 'package:aves/widgets/collection/collection_grid.dart'; import 'package:aves/widgets/collection/collection_grid.dart';
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/basic/scaffold.dart';
@ -154,9 +152,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
late final Future<void> _appSetup; late final Future<void> _appSetup;
late final Future<bool> _shouldUseBoldFontLoader; late final Future<bool> _shouldUseBoldFontLoader;
final TvRailController _tvRailController = TvRailController(); final TvRailController _tvRailController = TvRailController();
final CollectionSource _mediaStoreSource = MediaStoreSource(); final MediaStoreSource _mediaStoreSource = MediaStoreSource();
final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: ADurations.mediaContentChangeDebounceDelay);
final Set<String> _changedUris = {};
Size? _screenSize; Size? _screenSize;
final ValueNotifier<PageTransitionsBuilder> _pageTransitionsBuilderNotifier = ValueNotifier(defaultPageTransitionsBuilder); final ValueNotifier<PageTransitionsBuilder> _pageTransitionsBuilderNotifier = ValueNotifier(defaultPageTransitionsBuilder);
@ -184,7 +180,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
EquatableConfig.stringify = true; EquatableConfig.stringify = true;
_appSetup = _setup(); _appSetup = _setup();
_shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont(); _shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont();
_subscriptions.add(_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChanged(event as String?))); _subscriptions.add(_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _mediaStoreSource.onStoreChanged(event as String?)));
_subscriptions.add(_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?))); _subscriptions.add(_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?)));
_subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion())); _subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()));
_subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?))); _subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?)));
@ -399,6 +395,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
} }
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
RecentlyAddedFilter.updateNow(); RecentlyAddedFilter.updateNow();
_mediaStoreSource.checkForChanges();
break; break;
default: default:
break; break;
@ -614,21 +611,6 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
_mediaStoreSource.updateDerivedFilters(); _mediaStoreSource.updateDerivedFilters();
} }
void _onMediaStoreChanged(String? uri) {
if (uri != null) _changedUris.add(uri);
if (_changedUris.isNotEmpty) {
_mediaStoreChangeDebouncer(() async {
final todo = _changedUris.toSet();
_changedUris.clear();
final tempUris = await _mediaStoreSource.refreshUris(todo);
if (tempUris.isNotEmpty) {
_changedUris.addAll(tempUris);
_onMediaStoreChanged(null);
}
});
}
}
void _onError(String? error) => reportService.recordError(error, null); void _onError(String? error) => reportService.recordError(error, null);
} }

View file

@ -7,6 +7,7 @@ import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/trash.dart'; import 'package:aves/model/filters/trash.dart';
import 'package:aves/model/query.dart'; import 'package:aves/model/query.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
@ -382,6 +383,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
(action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection), (action) => _buildButtonIcon(context, action, enabled: canApply(action), selection: selection),
); );
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [ return [
...quickActionButtons, ...quickActionButtons,
PopupMenuButton<EntrySetAction>( PopupMenuButton<EntrySetAction>(
@ -432,9 +434,10 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
}, },
onSelected: (action) async { onSelected: (action) async {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
await _onActionSelected(action); await _onActionSelected(action);
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
]; ];
} }

View file

@ -11,6 +11,7 @@ import 'package:aves/widgets/common/action_mixins/overlay_snack_bar.dart';
import 'package:aves/widgets/common/basic/circle.dart'; import 'package:aves/widgets/common/basic/circle.dart';
import 'package:aves/widgets/common/basic/text/change_highlight.dart'; import 'package:aves/widgets/common/basic/text/change_highlight.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -202,7 +203,8 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme; final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final progressColor = colorScheme.primary; final progressColor = colorScheme.primary;
final animate = context.select<Settings, bool>((v) => v.accessibilityAnimations.animate); final animate = context.select<Settings, bool>((v) => v.accessibilityAnimations.animate);
return PopScope( return PopScope(
@ -223,7 +225,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
width: diameter + 2, width: diameter + 2,
height: diameter + 2, height: diameter + 2,
decoration: BoxDecoration( decoration: BoxDecoration(
color: colorScheme.brightness == Brightness.dark ? const Color(0xBB000000) : const Color(0xEEFFFFFF), color: theme.isDark ? const Color(0xBB000000) : const Color(0xEEFFFFFF),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
), ),

View file

@ -1,3 +1,4 @@
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// adapted from Flutter `SnackBar` in `/material/snack_bar.dart` // adapted from Flutter `SnackBar` in `/material/snack_bar.dart`
@ -115,7 +116,7 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme; final ColorScheme colorScheme = theme.colorScheme;
final SnackBarThemeData snackBarTheme = theme.snackBarTheme; final SnackBarThemeData snackBarTheme = theme.snackBarTheme;
final bool isThemeDark = theme.brightness == Brightness.dark; final bool isThemeDark = theme.isDark;
final Color buttonColor = isThemeDark ? colorScheme.primary : colorScheme.secondary; final Color buttonColor = isThemeDark ? colorScheme.primary : colorScheme.secondary;
final SnackBarThemeData defaults = _SnackbarDefaultsM3(context); final SnackBarThemeData defaults = _SnackbarDefaultsM3(context);

View file

@ -4,6 +4,7 @@ import 'package:aves/model/source/events.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -77,7 +78,7 @@ class SourceStateSubtitle extends StatelessWidget {
const WidgetSpan(child: SizedBox(width: 8)), const WidgetSpan(child: SizedBox(width: 8)),
TextSpan( TextSpan(
text: '${progress.done}/${progress.total}', text: '${progress.done}/${progress.total}',
style: TextStyle(color: theme.brightness == Brightness.dark ? Colors.white30 : Colors.black26), style: TextStyle(color: theme.isDark ? Colors.white30 : Colors.black26),
), ),
] ]
], ],

View file

@ -21,6 +21,7 @@ class AvesPopupMenuButton<T> extends PopupMenuButton<T> {
super.enableFeedback, super.enableFeedback,
super.iconSize, super.iconSize,
this.onMenuOpened, this.onMenuOpened,
super.popUpAnimationStyle,
}); });
@override @override

View file

@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
extension ExtraThemeData on ThemeData {
bool get isDark => brightness == Brightness.dark;
}

View file

@ -1,7 +1,8 @@
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AvesBorder { class AvesBorder {
static Color _borderColor(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Colors.white30 : Colors.black26; static Color _borderColor(BuildContext context) => Theme.of(context).isDark ? Colors.white30 : Colors.black26;
// 1 device pixel for straight lines is fine // 1 device pixel for straight lines is fine
static double straightBorderWidth(BuildContext context) => 1 / View.of(context).devicePixelRatio; static double straightBorderWidth(BuildContext context) => 1 / View.of(context).devicePixelRatio;

View file

@ -1,5 +1,6 @@
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart'; import 'package:aves_utils/aves_utils.dart';
@ -99,7 +100,7 @@ class _OverlayBackgroundState extends State<_OverlayBackground> {
} }
BoxDecoration _buildBackgroundDecoration(BuildContext context) { BoxDecoration _buildBackgroundDecoration(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).isDark;
final gradientCenter = widget.gradientCenter; final gradientCenter = widget.gradientCenter;
return _initialized return _initialized
? BoxDecoration( ? BoxDecoration(

View file

@ -1,4 +1,5 @@
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/grid/sections/mosaic/scale_grid.dart'; import 'package:aves/widgets/common/grid/sections/mosaic/scale_grid.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves_utils/aves_utils.dart'; import 'package:aves_utils/aves_utils.dart';
@ -104,7 +105,7 @@ class _OverlayBackgroundState extends State<_OverlayBackground> {
} }
BoxDecoration _buildBackgroundDecoration(BuildContext context) { BoxDecoration _buildBackgroundDecoration(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).isDark;
return _initialized return _initialized
? BoxDecoration( ? BoxDecoration(
color: isDark ? Colors.black87 : const Color(0xDDFFFFFF), color: isDark ? Colors.black87 : const Color(0xDDFFFFFF),

View file

@ -91,12 +91,12 @@ class GridThemeData {
if (located && showLocated) LocationIcon.located(), if (located && showLocated) LocationIcon.located(),
if (!located && showUnlocated) LocationIcon.unlocated(), if (!located && showUnlocated) LocationIcon.unlocated(),
if (entry.rating != 0 && showRating) RatingIcon(entry: entry), if (entry.rating != 0 && showRating) RatingIcon(entry: entry),
if (entry.isHdr && showHdr) const HdrIcon(),
if (entry.isPureVideo) if (entry.isPureVideo)
VideoIcon(entry: entry) VideoIcon(entry: entry)
else if (entry.isAnimated) else if (entry.isAnimated)
const AnimatedImageIcon() const AnimatedImageIcon()
else ...[ else ...[
if (entry.isHdr && showHdr) const HdrIcon(),
if (entry.isRaw && showRaw) const RawIcon(), if (entry.isRaw && showRaw) const RawIcon(),
if (entry.is360) const PanoramaIcon(), if (entry.is360) const PanoramaIcon(),
], ],

View file

@ -11,7 +11,6 @@ import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/collection/filter_bar.dart';
@ -117,6 +116,7 @@ class AvesFilterChip extends StatefulWidget {
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension); const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
final actionDelegate = ChipActionDelegate(); final actionDelegate = ChipActionDelegate();
final animations = context.read<Settings>().accessibilityAnimations;
final selectedAction = await showMenu<ChipAction>( final selectedAction = await showMenu<ChipAction>(
context: context, context: context,
position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size), position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size),
@ -149,10 +149,11 @@ class AvesFilterChip extends StatefulWidget {
); );
}), }),
], ],
popUpAnimationStyle: animations.popUpAnimationStyle,
); );
if (selectedAction != null) { if (selectedAction != null) {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
actionDelegate.onActionSelected(context, filter, selectedAction); actionDelegate.onActionSelected(context, filter, selectedAction);
} }
} }

View file

@ -6,6 +6,7 @@ import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/model/vaults/vaults.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/grid/theme.dart'; import 'package:aves/widgets/common/grid/theme.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -295,7 +296,7 @@ class OverlayIcon extends StatelessWidget {
margin: margin, margin: margin,
padding: text != null ? EdgeInsetsDirectional.only(end: size / 4) : null, padding: text != null ? EdgeInsetsDirectional.only(end: size / 4) : null,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark ? const Color(0xAA000000) : const Color(0xCCFFFFFF), color: Theme.of(context).isDark ? const Color(0xAA000000) : const Color(0xCCFFFFFF),
borderRadius: BorderRadius.all(Radius.circular(size)), borderRadius: BorderRadius.all(Radius.circular(size)),
), ),
child: text == null child: text == null

View file

@ -1,5 +1,6 @@
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/fx/borders.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -174,7 +175,7 @@ class OverlayTextButton extends StatelessWidget {
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred)), backgroundColor: MaterialStateProperty.all<Color>(Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred)),
foregroundColor: MaterialStateProperty.all<Color>(theme.colorScheme.onSurface), foregroundColor: MaterialStateProperty.all<Color>(theme.colorScheme.onSurface),
overlayColor: theme.brightness == Brightness.dark ? MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)) : null, overlayColor: theme.isDark ? MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)) : null,
minimumSize: _minSize, minimumSize: _minSize,
side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide(context)), side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide(context)),
shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder( shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder(

View file

@ -2,6 +2,7 @@
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/widgets/common/basic/text/outlined.dart'; import 'package:aves/widgets/common/basic/text/outlined.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/fx/highlight_decoration.dart'; import 'package:aves/widgets/common/fx/highlight_decoration.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -27,7 +28,7 @@ class HighlightTitle extends StatelessWidget {
static List<Shadow> shadows(BuildContext context) => [ static List<Shadow> shadows(BuildContext context) => [
Shadow( Shadow(
color: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white, color: Theme.of(context).isDark ? Colors.black : Colors.white,
offset: const Offset(0, 1), offset: const Offset(0, 1),
blurRadius: 2, blurRadius: 2,
) )

View file

@ -4,9 +4,9 @@ import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/path.dart';
import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/basic/font_size_icon_theme.dart'; import 'package:aves/widgets/common/basic/font_size_icon_theme.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/basic/scaffold.dart';
@ -25,6 +25,7 @@ import 'package:aves/widgets/debug/media_store_scan_dialog.dart';
import 'package:aves/widgets/debug/report.dart'; import 'package:aves/widgets/debug/report.dart';
import 'package:aves/widgets/debug/settings.dart'; import 'package:aves/widgets/debug/settings.dart';
import 'package:aves/widgets/debug/storage.dart'; import 'package:aves/widgets/debug/storage.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -36,6 +37,7 @@ class AppDebugPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return Directionality( return Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: AvesScaffold( child: AvesScaffold(
@ -56,9 +58,10 @@ class AppDebugPage extends StatelessWidget {
.toList(), .toList(),
onSelected: (action) async { onSelected: (action) async {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
unawaited(_onActionSelected(context, action)); unawaited(_onActionSelected(context, action));
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
), ),
], ],

View file

@ -2,8 +2,8 @@ import 'dart:math';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/naming_pattern.dart'; import 'package:aves/model/naming_pattern.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/theme/styles.dart'; import 'package:aves/theme/styles.dart';
import 'package:aves/widgets/collection/collection_grid.dart'; import 'package:aves/widgets/collection/collection_grid.dart';
@ -14,8 +14,10 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/grid/theme.dart'; import 'package:aves/widgets/common/grid/theme.dart';
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart'; import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
import 'package:aves/widgets/common/thumbnail/decorated.dart'; import 'package:aves/widgets/common/thumbnail/decorated.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class RenameEntrySetPage extends StatefulWidget { class RenameEntrySetPage extends StatefulWidget {
static const routeName = '/rename_entry_set'; static const routeName = '/rename_entry_set';
@ -62,6 +64,7 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
final l10n = context.l10n; final l10n = context.l10n;
final textScaler = MediaQuery.textScalerOf(context); final textScaler = MediaQuery.textScalerOf(context);
final effectiveThumbnailExtent = max(thumbnailExtent, textScaler.scale(thumbnailExtent)); final effectiveThumbnailExtent = max(thumbnailExtent, textScaler.scale(thumbnailExtent));
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return AvesScaffold( return AvesScaffold(
appBar: AppBar( appBar: AppBar(
title: Text(l10n.renameEntrySetPageTitle), title: Text(l10n.renameEntrySetPageTitle),
@ -104,11 +107,12 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
}, },
onSelected: (key) async { onSelected: (key) async {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
_insertProcessor(key); _insertProcessor(key);
}, },
tooltip: l10n.renameEntrySetPageInsertTooltip, tooltip: l10n.renameEntrySetPageInsertTooltip,
icon: const Icon(AIcons.add), icon: const Icon(AIcons.add),
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
), ),
], ],

View file

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/thumbnail/image.dart'; import 'package:aves/widgets/common/thumbnail/image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -50,7 +51,7 @@ class ItemPicker extends StatelessWidget {
bottom: -1, bottom: -1,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark ? const Color(0xAA000000) : const Color(0xCCFFFFFF), color: Theme.of(context).isDark ? const Color(0xAA000000) : const Color(0xCCFFFFFF),
border: AvesBorder.border(context), border: AvesBorder.border(context),
borderRadius: actionBoxBorderRadius, borderRadius: actionBoxBorderRadius,
), ),

View file

@ -2,6 +2,7 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/album.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
@ -205,6 +206,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
required bool Function(ChipSetAction action) isVisible, required bool Function(ChipSetAction action) isVisible,
required void Function(ChipSetAction action) onActionSelected, required void Function(ChipSetAction action) onActionSelected,
}) { }) {
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [ return [
if (widget.moveType != null) if (widget.moveType != null)
..._quickActions.where(isVisible).map( ..._quickActions.where(isVisible).map(
@ -227,9 +229,10 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
onActionSelected(action); onActionSelected(action);
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
]; ];
} }

View file

@ -4,6 +4,7 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/query.dart'; import 'package:aves/model/query.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
@ -329,6 +330,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
(action) => _buildButtonIcon(context, actionDelegate, action, enabled: canApply(action)), (action) => _buildButtonIcon(context, actionDelegate, action, enabled: canApply(action)),
); );
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [ return [
...quickActionButtons, ...quickActionButtons,
PopupMenuButton<ChipSetAction>( PopupMenuButton<ChipSetAction>(
@ -366,9 +368,10 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
_onActionSelected(context, action, actionDelegate); _onActionSelected(context, action, actionDelegate);
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
]; ];
} }

View file

@ -7,6 +7,7 @@ import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/geotiff.dart'; import 'package:aves/model/geotiff.dart';
import 'package:aves/model/highlight.dart'; import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -462,6 +463,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
) async { ) async {
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension); const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
final animations = context.read<Settings>().accessibilityAnimations;
final selectedAction = await showMenu<MapClusterAction>( final selectedAction = await showMenu<MapClusterAction>(
context: context, context: context,
position: RelativeRect.fromRect(tapLocalPosition & touchArea, Offset.zero & overlay.size), position: RelativeRect.fromRect(tapLocalPosition & touchArea, Offset.zero & overlay.size),
@ -482,10 +484,11 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
MapClusterAction.removeLocation, MapClusterAction.removeLocation,
].map(_buildMenuItem), ].map(_buildMenuItem),
], ],
popUpAnimationStyle: animations.popUpAnimationStyle,
); );
if (selectedAction != null) { if (selectedAction != null) {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
final delegate = EntrySetActionDelegate(); final delegate = EntrySetActionDelegate();
switch (selectedAction) { switch (selectedAction) {
case MapClusterAction.editLocation: case MapClusterAction.editLocation:

View file

@ -1,5 +1,6 @@
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/theme/styles.dart'; import 'package:aves/theme/styles.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:decorated_icon/decorated_icon.dart'; import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -16,7 +17,8 @@ class SettingsTileLeading extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme; final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return AnimatedContainer( return AnimatedContainer(
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -32,7 +34,7 @@ class SettingsTileLeading extends StatelessWidget {
icon, icon,
size: 18, size: 18,
color: DefaultTextStyle.of(context).style.color, color: DefaultTextStyle.of(context).style.color,
shadows: colorScheme.brightness == Brightness.dark ? AStyles.embossShadows : null, shadows: theme.isDark ? AStyles.embossShadows : null,
), ),
); );
} }

View file

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
@ -18,6 +19,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class FilePickerPage extends StatefulWidget { class FilePickerPage extends StatefulWidget {
static const routeName = '/file_picker'; static const routeName = '/file_picker';
@ -57,6 +59,7 @@ class _FilePickerPageState extends State<FilePickerPage> {
return !isHidden; return !isHidden;
} }
}).toList(); }).toList();
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return PopScope( return PopScope(
canPop: _directory.relativeDir.isEmpty, canPop: _directory.relativeDir.isEmpty,
onPopInvoked: (didPop) { onPopInvoked: (didPop) {
@ -82,13 +85,14 @@ class _FilePickerPageState extends State<FilePickerPage> {
}, },
onSelected: (action) async { onSelected: (action) async {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
switch (action) { switch (action) {
case _PickerAction.toggleHiddenView: case _PickerAction.toggleHiddenView:
settings.filePickerShowHiddenFiles = !showHidden; settings.filePickerShowHiddenFiles = !showHidden;
setState(() {}); setState(() {});
} }
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
), ),
], ],

View file

@ -1,10 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart';
@ -45,6 +46,7 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> with FeedbackMi
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return AvesScaffold( return AvesScaffold(
appBar: AppBar( appBar: AppBar(
title: InteractiveAppBarTitle( title: InteractiveAppBarTitle(
@ -72,9 +74,10 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> with FeedbackMi
}, },
onSelected: (action) async { onSelected: (action) async {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
_onActionSelected(action); _onActionSelected(action);
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
].map((v) => FontSizeIconTheme(child: v)).toList(), ].map((v) => FontSizeIconTheme(child: v)).toList(),
), ),

View file

@ -1,5 +1,6 @@
import 'package:aves/theme/styles.dart'; import 'package:aves/theme/styles.dart';
import 'package:aves/widgets/common/basic/text/outlined.dart'; import 'package:aves/widgets/common/basic/text/outlined.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -20,7 +21,7 @@ class LinearPercentIndicatorText extends StatelessWidget {
TextSpan( TextSpan(
text: percentFormat.format(percent), text: percentFormat.format(percent),
style: TextStyle( style: TextStyle(
shadows: theme.brightness == Brightness.dark ? AStyles.embossShadows : null, shadows: theme.isDark ? AStyles.embossShadows : null,
), ),
) )
], ],

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/multipage.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/viewer/controls/cast.dart'; import 'package:aves/widgets/viewer/controls/cast.dart';

View file

@ -11,6 +11,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/behaviour/springy_scroll_physics.dart'; import 'package:aves/widgets/common/behaviour/springy_scroll_physics.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/viewer/action/entry_action_delegate.dart'; import 'package:aves/widgets/viewer/action/entry_action_delegate.dart';
import 'package:aves/widgets/viewer/controls/controller.dart'; import 'package:aves/widgets/viewer/controls/controller.dart';
import 'package:aves/widgets/viewer/controls/intents.dart'; import 'package:aves/widgets/viewer/controls/intents.dart';
@ -171,7 +172,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
return ValueListenableBuilder<double>( return ValueListenableBuilder<double>(
valueListenable: widget.overlayOpacity, valueListenable: widget.overlayOpacity,
builder: (context, overlayOpacity, child) { builder: (context, overlayOpacity, child) {
final background = Theme.of(context).brightness == Brightness.dark ? Colors.black : Color.lerp(Colors.black, Colors.white, overlayOpacity)!; final background = Theme.of(context).isDark ? Colors.black : Color.lerp(Colors.black, Colors.white, overlayOpacity)!;
return Container( return Container(
color: background.withOpacity(backgroundOpacity), color: background.withOpacity(backgroundOpacity),
child: child, child: child,

View file

@ -1,6 +1,7 @@
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/viewer/controls/controller.dart'; import 'package:aves/widgets/viewer/controls/controller.dart';
import 'package:aves/widgets/viewer/entry_viewer_stack.dart'; import 'package:aves/widgets/viewer/entry_viewer_stack.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart'; import 'package:aves/widgets/viewer/overlay/bottom.dart';
@ -26,6 +27,8 @@ class EntryViewerPage extends StatefulWidget {
static EdgeInsets snackBarMargin(BuildContext context) { static EdgeInsets snackBarMargin(BuildContext context) {
return EdgeInsets.only(bottom: ViewerBottomOverlay.actionSafeHeight(context)); return EdgeInsets.only(bottom: ViewerBottomOverlay.actionSafeHeight(context));
} }
static Color getBackground(BuildContext context) => Theme.of(context).isDark ? Colors.black : Colors.white;
} }
class _EntryViewerPageState extends State<EntryViewerPage> { class _EntryViewerPageState extends State<EntryViewerPage> {
@ -56,11 +59,7 @@ class _EntryViewerPageState extends State<EntryViewerPage> {
viewerController: _viewerController, viewerController: _viewerController,
), ),
), ),
backgroundColor: Navigator.canPop(context) backgroundColor: Navigator.canPop(context) ? Colors.transparent : EntryViewerPage.getBackground(context),
? Colors.transparent
: Theme.of(context).brightness == Brightness.dark
? Colors.black
: Colors.white,
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
); );
} }

View file

@ -2,9 +2,9 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
@ -49,6 +49,7 @@ class InfoAppBar extends StatelessWidget {
final commonActions = EntryActions.commonMetadataActions.where(isVisible); final commonActions = EntryActions.commonMetadataActions.where(isVisible);
final formatSpecificActions = EntryActions.formatSpecificMetadataActions.where(isVisible); final formatSpecificActions = EntryActions.formatSpecificMetadataActions.where(isVisible);
final useTvLayout = settings.useTvLayout; final useTvLayout = settings.useTvLayout;
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return SliverAppBar( return SliverAppBar(
leading: useTvLayout leading: useTvLayout
? null ? null
@ -91,9 +92,10 @@ class InfoAppBar extends StatelessWidget {
], ],
onSelected: (action) async { onSelected: (action) async {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
actionDelegate.onActionSelected(context, entry, collection, action); actionDelegate.onActionSelected(context, entry, collection, action);
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
].map((v) => FontSizeIconTheme(child: v)).toList(), ].map((v) => FontSizeIconTheme(child: v)).toList(),
floating: true, floating: true,

View file

@ -6,6 +6,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/theme/styles.dart'; import 'package:aves/theme/styles.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/overlay/details/date.dart'; import 'package:aves/widgets/viewer/overlay/details/date.dart';
import 'package:aves/widgets/viewer/overlay/details/description.dart'; import 'package:aves/widgets/viewer/overlay/details/description.dart';
@ -136,7 +137,7 @@ class ViewerDetailOverlayContent extends StatelessWidget {
static const double iconPadding = 8.0; static const double iconPadding = 8.0;
static const double iconSize = 16.0; static const double iconSize = 16.0;
static List<Shadow>? shadows(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? AStyles.embossShadows : null; static List<Shadow>? shadows(BuildContext context) => Theme.of(context).isDark ? AStyles.embossShadows : null;
const ViewerDetailOverlayContent({ const ViewerDetailOverlayContent({
super.key, super.key,

View file

@ -5,6 +5,7 @@ import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/theme/styles.dart'; import 'package:aves/theme/styles.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves_video/aves_video.dart'; import 'package:aves_video/aves_video.dart';
@ -39,9 +40,9 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final blurred = settings.enableBlurEffect; final blurred = settings.enableBlurEffect;
final brightness = Theme.of(context).brightness; final theme = Theme.of(context);
final textStyle = TextStyle( final textStyle = TextStyle(
shadows: brightness == Brightness.dark ? AStyles.embossShadows : null, shadows: theme.isDark ? AStyles.embossShadows : null,
); );
const strutStyle = StrutStyle( const strutStyle = StrutStyle(
forceStrutHeight: true, forceStrutHeight: true,
@ -71,7 +72,7 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
alignment: Alignment.center, alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Themes.overlayBackgroundColor(brightness: brightness, blurred: blurred), color: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
border: AvesBorder.border(context), border: AvesBorder.border(context),
borderRadius: const BorderRadius.all(Radius.circular(radius)), borderRadius: const BorderRadius.all(Radius.circular(radius)),
), ),

View file

@ -4,9 +4,9 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/multipage.dart';
import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/move_button.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/move_button.dart';
@ -250,6 +250,7 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
final exportActions = widget.exportActions; final exportActions = widget.exportActions;
final videoActions = widget.videoActions; final videoActions = widget.videoActions;
final hasOverflowMenu = pageEntry.canRotate || pageEntry.canFlip || topLevelActions.isNotEmpty || exportActions.isNotEmpty || videoActions.isNotEmpty; final hasOverflowMenu = pageEntry.canRotate || pageEntry.canFlip || topLevelActions.isNotEmpty || exportActions.isNotEmpty || videoActions.isNotEmpty;
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return Selector<VideoConductor, AvesVideoController?>( return Selector<VideoConductor, AvesVideoController?>(
selector: (context, vc) => vc.getController(pageEntry), selector: (context, vc) => vc.getController(pageEntry),
builder: (context, videoController, child) { builder: (context, videoController, child) {
@ -301,10 +302,11 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
] ]
]; ];
}, },
onSelected: (action) { onSelected: (action) async {
_popupExpandedNotifier.value = null; _popupExpandedNotifier.value = null;
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
Future.delayed(ADurations.popupMenuAnimation * timeDilation, () => widget.actionDelegate.onActionSelected(context, action)); await Future.delayed(animations.popUpAnimationDelay * timeDilation);
widget.actionDelegate.onActionSelected(context, action);
}, },
onCanceled: () { onCanceled: () {
_popupExpandedNotifier.value = null; _popupExpandedNotifier.value = null;
@ -316,6 +318,7 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
// so we make sure overlay stays visible // so we make sure overlay stays visible
const ToggleOverlayNotification(visible: true).dispatch(context); const ToggleOverlayNotification(visible: true).dispatch(context);
}, },
popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
), ),
), ),

View file

@ -4,6 +4,7 @@ import 'package:aves/model/entry/entry.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -40,7 +41,7 @@ class _ErrorViewState extends State<ErrorView> {
// use container to expand constraints, so that the user can tap anywhere // use container to expand constraints, so that the user can tap anywhere
child: Container( child: Container(
// opaque to cover potential lower quality layer below // opaque to cover potential lower quality layer below
color: Colors.black, color: EntryViewerPage.getBackground(context),
child: FutureBuilder<bool>( child: FutureBuilder<bool>(
future: _exists, future: _exists,
builder: (context, snapshot) { builder: (context, snapshot) {

View file

@ -7,6 +7,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/viewer/action/video_action_delegate.dart'; import 'package:aves/widgets/viewer/action/video_action_delegate.dart';
import 'package:aves/widgets/viewer/controls/controller.dart'; import 'package:aves/widgets/viewer/controls/controller.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart';
@ -50,7 +51,7 @@ class WallpaperPage extends StatelessWidget {
), ),
) )
: const SizedBox(), : const SizedBox(),
backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white, backgroundColor: Theme.of(context).isDark ? Colors.black : Colors.white,
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
); );
} }

View file

@ -81,10 +81,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter

View file

@ -137,10 +137,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logger name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2+1" version: "2.1.0"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -201,10 +201,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -262,10 +262,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.1"
wkt_parser: wkt_parser:
dependency: transitive dependency: transitive
description: description:

View file

@ -5,10 +5,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: "737321f9be522620ed3794937298fb0027a48a402624fa2500f7532f94aea810" sha256: "4eec93681221723a686ad580c2e7d960e1017cf1a4e0a263c2573c2c6b0bf5cd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.22" version: "1.3.25"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -68,10 +68,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: "7e049e32a9d347616edb39542cf92cd53fdb4a99fb6af0a0bff327c14cd76445" sha256: "53316975310c8af75a96e365f9fccb67d1c544ef0acdbf0d88bbe30eedd1c4f9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.25.4" version: "2.27.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -84,26 +84,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: "57e61d6010e253b36d38191cefd6199d7849152cdcd234b61ca290cdb278a0ba" sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.4" version: "2.11.5"
firebase_crashlytics: firebase_crashlytics:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_crashlytics name: firebase_crashlytics
sha256: "4c754db28a7daabe03c4cbf1079dbe81e6f0681284fed6d07e0e640b7f1027c4" sha256: c4f1b723d417bc9c4774810e774ff91df8fb0032d33fb2888b2c887e865581b8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.15" version: "3.4.18"
firebase_crashlytics_platform_interface: firebase_crashlytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics_platform_interface name: firebase_crashlytics_platform_interface
sha256: "08c5d7b5f93dbad7306d26702935abd8b579313ea256eb27006562a1867df249" sha256: c5a11fca3df76a98e3fa68fde8b10a08aacb9a7639f619fbfd4dad6c67a08643
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.6.22" version: "3.6.25"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter

View file

@ -144,10 +144,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logger name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2+1" version: "2.1.0"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -208,10 +208,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -269,10 +269,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.1"
wkt_parser: wkt_parser:
dependency: transitive dependency: transitive
description: description:

View file

@ -53,7 +53,7 @@ class EntryGoogleMap<T> extends StatefulWidget {
State<StatefulWidget> createState() => _EntryGoogleMapState<T>(); State<StatefulWidget> createState() => _EntryGoogleMapState<T>();
} }
class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindingObserver { class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> {
GoogleMapController? _serviceMapController; GoogleMapController? _serviceMapController;
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
Map<MarkerKey<T>, GeoEntry<T>> _geoEntryByMarkerKey = {}; Map<MarkerKey<T>, GeoEntry<T>> _geoEntryByMarkerKey = {};
@ -72,7 +72,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
_sizeNotifier.addListener(_onSizeChanged); _sizeNotifier.addListener(_onSizeChanged);
_registerWidget(widget); _registerWidget(widget);
} }
@ -88,7 +87,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
void dispose() { void dispose() {
_unregisterWidget(widget); _unregisterWidget(widget);
_serviceMapController?.dispose(); _serviceMapController?.dispose();
WidgetsBinding.instance.removeObserver(this);
_sizeNotifier.dispose(); _sizeNotifier.dispose();
super.dispose(); super.dispose();
} }
@ -109,15 +107,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
..clear(); ..clear();
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// workaround for blank map when resuming app
// cf https://github.com/flutter/flutter/issues/40284
_serviceMapController?.setMapStyle(null);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(

View file

@ -187,50 +187,50 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps name: google_maps
sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" sha256: "47eef3836b49bb030d5cb3afc60b8451408bf34cf753e571b645d6529eb4251a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.0" version: "7.1.0"
google_maps_flutter: google_maps_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: google_maps_flutter name: google_maps_flutter
sha256: ae66fef3e71261d7df2eff29b2a119e190b2884325ecaa55321b1e17b5504066 sha256: "982c2e22ec78d32701bfbd351311e8579a4952035381ac66462c0af11003107b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.3" version: "2.6.0"
google_maps_flutter_android: google_maps_flutter_android:
dependency: "direct main" dependency: "direct main"
description: description:
name: google_maps_flutter_android name: google_maps_flutter_android
sha256: "714530f865f13bb3b9505c58821c3baed5d247a871724acf5d2ea5808fbed02c" sha256: "256b3c974e415bd17555ceff76a5d0badd2cbfd29febfc23070993358f639550"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.2" version: "2.7.0"
google_maps_flutter_ios: google_maps_flutter_ios:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_ios name: google_maps_flutter_ios
sha256: "29503b5159da2308a66212c3827963998bfb943ba073e2114fb2d486b47fd2c8" sha256: "0997f99d8bd8712f648a49bfc96a3cf2713cfdaf73a005c719aab74eaef94030"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
google_maps_flutter_platform_interface: google_maps_flutter_platform_interface:
dependency: "direct main" dependency: "direct main"
description: description:
name: google_maps_flutter_platform_interface name: google_maps_flutter_platform_interface
sha256: "6060779f020638a8eedeb0fb14234818e5fa32ec45a4653d6428ab436e2bbc64" sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "2.6.0"
google_maps_flutter_web: google_maps_flutter_web:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_web name: google_maps_flutter_web
sha256: "6245721c160d6f531c1ef568cf9bef8d660cd585a982aa75121269030163785a" sha256: "861c6dd430123e58bb1154342345c5bfa36064120c8ec3dbc0d0b7522add1861"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.4+3" version: "0.5.6+2"
html: html:
dependency: transitive dependency: transitive
description: description:
@ -307,10 +307,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logger name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2+1" version: "2.1.0"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -379,10 +379,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
sanitize_html: sanitize_html:
dependency: transitive dependency: transitive
description: description:
@ -456,18 +456,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.0" version: "5.3.0"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
@ -486,4 +486,4 @@ packages:
version: "2.0.0" version: "2.0.0"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.6" flutter: ">=3.19.0"

View file

@ -183,10 +183,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logger name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2+1" version: "2.1.0"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -255,10 +255,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -316,10 +316,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.1"
wkt_parser: wkt_parser:
dependency: transitive dependency: transitive
description: description:

View file

@ -151,10 +151,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logger name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2+1" version: "2.1.0"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -215,10 +215,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -276,10 +276,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.1"
wkt_parser: wkt_parser:
dependency: transitive dependency: transitive
description: description:

View file

@ -441,10 +441,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.0" version: "5.3.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: "737321f9be522620ed3794937298fb0027a48a402624fa2500f7532f94aea810" sha256: "4eec93681221723a686ad580c2e7d960e1017cf1a4e0a263c2573c2c6b0bf5cd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.22" version: "1.3.25"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
@ -314,10 +314,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dynamic_color name: dynamic_color
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.9" version: "1.7.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@ -388,10 +388,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core name: firebase_core
sha256: "7e049e32a9d347616edb39542cf92cd53fdb4a99fb6af0a0bff327c14cd76445" sha256: "53316975310c8af75a96e365f9fccb67d1c544ef0acdbf0d88bbe30eedd1c4f9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.25.4" version: "2.27.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -404,26 +404,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: "57e61d6010e253b36d38191cefd6199d7849152cdcd234b61ca290cdb278a0ba" sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.4" version: "2.11.5"
firebase_crashlytics: firebase_crashlytics:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics name: firebase_crashlytics
sha256: "4c754db28a7daabe03c4cbf1079dbe81e6f0681284fed6d07e0e640b7f1027c4" sha256: c4f1b723d417bc9c4774810e774ff91df8fb0032d33fb2888b2c887e865581b8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.15" version: "3.4.18"
firebase_crashlytics_platform_interface: firebase_crashlytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics_platform_interface name: firebase_crashlytics_platform_interface
sha256: "08c5d7b5f93dbad7306d26702935abd8b579313ea256eb27006562a1867df249" sha256: c5a11fca3df76a98e3fa68fde8b10a08aacb9a7639f619fbfd4dad6c67a08643
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.6.22" version: "3.6.25"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -436,10 +436,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flex_color_picker name: flex_color_picker
sha256: "0871edc170153cfc3de316d30625f40a85daecfa76ce541641f3cc0ec7757cbf" sha256: "904373c7b0531fd4a92d29705a80ab4594b7647da2d93044487aaec4614cb6ed"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.1" version: "3.4.0"
flex_seed_scheme: flex_seed_scheme:
dependency: transitive dependency: transitive
description: description:
@ -452,10 +452,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: floating name: floating
sha256: d9d563089e34fbd714ffdcdd2df447ec41b40c9226dacae6b4f78847aef8b991 sha256: "04c3c96909b94dd6d2d121c69707739825e1f3dceca5ae451a9b8c0e652d246b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
fluster: fluster:
dependency: "direct main" dependency: "direct main"
description: description:
@ -532,10 +532,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: a64c5323ac83ed2b7940d2b6288d160aa1753ff271ba9d9b2a86770414aa3eab sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.20+1" version: "0.6.21"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -635,26 +635,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_android name: google_maps_flutter_android
sha256: "714530f865f13bb3b9505c58821c3baed5d247a871724acf5d2ea5808fbed02c" sha256: "256b3c974e415bd17555ceff76a5d0badd2cbfd29febfc23070993358f639550"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.2" version: "2.7.0"
google_maps_flutter_ios: google_maps_flutter_ios:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_ios name: google_maps_flutter_ios
sha256: "29503b5159da2308a66212c3827963998bfb943ba073e2114fb2d486b47fd2c8" sha256: "0997f99d8bd8712f648a49bfc96a3cf2713cfdaf73a005c719aab74eaef94030"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
google_maps_flutter_platform_interface: google_maps_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_platform_interface name: google_maps_flutter_platform_interface
sha256: "6060779f020638a8eedeb0fb14234818e5fa32ec45a4653d6428ab436e2bbc64" sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "2.6.0"
google_maps_flutter_web: google_maps_flutter_web:
dependency: transitive dependency: transitive
description: description:
@ -835,10 +835,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: logger name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2+1" version: "2.1.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -851,10 +851,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: markdown name: markdown
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90" sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.1" version: "7.2.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -1237,10 +1237,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -1619,10 +1619,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.4" version: "6.2.5"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@ -1755,10 +1755,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.0" version: "5.3.0"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
@ -1801,4 +1801,4 @@ packages:
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.1" flutter: ">=3.19.3"

View file

@ -7,13 +7,13 @@ repository: https://github.com/deckerst/aves
# - play changelog: /whatsnew/whatsnew-en-US # - play changelog: /whatsnew/whatsnew-en-US
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt # - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
version: 1.10.5+114 version: 1.10.6+115
publish_to: none publish_to: none
environment: environment:
# this project bundles Flutter SDK via `flutter_wrapper` # this project bundles Flutter SDK via `flutter_wrapper`
# cf https://github.com/passsy/flutter_wrapper # cf https://github.com/passsy/flutter_wrapper
flutter: 3.19.1 flutter: 3.19.3
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'
# use `scripts/apply_flavor_{flavor}.sh` to set the right dependencies for the flavor # use `scripts/apply_flavor_{flavor}.sh` to set the right dependencies for the flavor

View file

@ -7,37 +7,37 @@ cd /d %sdk%\platform-tools
@echo on @echo on
adb.exe shell setprop log.tag.ACodec WARN adb.exe shell setprop persist.log.tag.ACodec WARN
adb.exe shell setprop log.tag.AHierarchicalStateMachine ERROR adb.exe shell setprop persist.log.tag.AHierarchicalStateMachine ERROR
adb.exe shell setprop log.tag.AudioCapabilities ERROR adb.exe shell setprop persist.log.tag.AudioCapabilities ERROR
adb.exe shell setprop log.tag.AudioTrack INFO adb.exe shell setprop persist.log.tag.AudioTrack INFO
adb.exe shell setprop log.tag.BufferPoolAccessor2.0 INFO adb.exe shell setprop persist.log.tag.BufferPoolAccessor2.0 INFO
adb.exe shell setprop log.tag.CCodec INFO adb.exe shell setprop persist.log.tag.CCodec INFO
adb.exe shell setprop log.tag.CCodecBufferChannel INFO adb.exe shell setprop persist.log.tag.CCodecBufferChannel INFO
adb.exe shell setprop log.tag.CCodecBuffers INFO adb.exe shell setprop persist.log.tag.CCodecBuffers INFO
adb.exe shell setprop log.tag.CCodecConfig INFO adb.exe shell setprop persist.log.tag.CCodecConfig INFO
adb.exe shell setprop log.tag.Codec2Client INFO adb.exe shell setprop persist.log.tag.Codec2Client INFO
adb.exe shell setprop log.tag.CompatibilityChangeReporter INFO adb.exe shell setprop persist.log.tag.CompatibilityChangeReporter INFO
adb.exe shell setprop log.tag.Counters WARN adb.exe shell setprop persist.log.tag.Counters WARN
adb.exe shell setprop log.tag.CustomizedTextParser INFO adb.exe shell setprop persist.log.tag.CustomizedTextParser INFO
adb.exe shell setprop log.tag.EGL_emulation INFO adb.exe shell setprop persist.log.tag.EGL_emulation INFO
adb.exe shell setprop log.tag.HostConnection INFO adb.exe shell setprop persist.log.tag.HostConnection INFO
adb.exe shell setprop log.tag.InputMethodManager WARN adb.exe shell setprop persist.log.tag.InputMethodManager WARN
adb.exe shell setprop log.tag.InsetsSourceConsumer INFO adb.exe shell setprop persist.log.tag.InsetsSourceConsumer INFO
adb.exe shell setprop log.tag.InputTransport INFO adb.exe shell setprop persist.log.tag.InputTransport INFO
adb.exe shell setprop log.tag.J4A INFO adb.exe shell setprop persist.log.tag.J4A INFO
adb.exe shell setprop log.tag.MediaCodec WARN adb.exe shell setprop persist.log.tag.MediaCodec WARN
adb.exe shell setprop log.tag.MediaMetadataRetriever INFO adb.exe shell setprop persist.log.tag.MediaMetadataRetriever INFO
adb.exe shell setprop log.tag.MediaMetadataRetrieverJNI INFO adb.exe shell setprop persist.log.tag.MediaMetadataRetrieverJNI INFO
adb.exe shell setprop log.tag.NativeTiffDecoder INFO adb.exe shell setprop persist.log.tag.NativeTiffDecoder INFO
adb.exe shell setprop log.tag.NuMediaExtractor INFO adb.exe shell setprop persist.log.tag.NuMediaExtractor INFO
adb.exe shell setprop log.tag.PipelineWatcher INFO adb.exe shell setprop persist.log.tag.PipelineWatcher INFO
adb.exe shell setprop log.tag.ReflectedParamUpdater INFO adb.exe shell setprop persist.log.tag.ReflectedParamUpdater INFO
adb.exe shell setprop log.tag.skia INFO adb.exe shell setprop persist.log.tag.skia INFO
adb.exe shell setprop log.tag.SurfaceControl WARN adb.exe shell setprop persist.log.tag.SurfaceControl WARN
adb.exe shell setprop log.tag.SurfaceUtils INFO adb.exe shell setprop persist.log.tag.SurfaceUtils INFO
adb.exe shell setprop log.tag.SurfaceView WARN adb.exe shell setprop persist.log.tag.SurfaceView WARN
adb.exe shell setprop log.tag.VideoCapabilities ERROR adb.exe shell setprop persist.log.tag.VideoCapabilities ERROR
@echo off @echo off
endlocal endlocal

View file

@ -1,32 +1,36 @@
#!/bin/bash #!/bin/bash
adb shell setprop log.tag.ACodec WARN adb shell setprop persist.log.tag.ACodec WARN
adb shell setprop log.tag.AHierarchicalStateMachine ERROR adb shell setprop persist.log.tag.AHierarchicalStateMachine ERROR
adb shell setprop log.tag.AudioCapabilities ERROR adb shell setprop persist.log.tag.AudioCapabilities ERROR
adb shell setprop log.tag.AudioTrack INFO adb shell setprop persist.log.tag.AudioTrack INFO
adb shell setprop log.tag.BufferPoolAccessor2.0 INFO adb shell setprop persist.log.tag.BufferPoolAccessor2.0 INFO
adb shell setprop log.tag.CCodec INFO adb shell setprop persist.log.tag.CCodec INFO
adb shell setprop log.tag.CCodecBufferChannel INFO adb shell setprop persist.log.tag.CCodecBufferChannel INFO
adb shell setprop log.tag.CCodecBuffers INFO adb shell setprop persist.log.tag.CCodecBuffers INFO
adb shell setprop log.tag.CCodecConfig INFO adb shell setprop persist.log.tag.CCodecConfig INFO
adb shell setprop log.tag.Codec2Client INFO adb shell setprop persist.log.tag.Codec2Client INFO
adb shell setprop log.tag.CompatibilityChangeReporter INFO adb shell setprop persist.log.tag.CompatibilityChangeReporter INFO
adb shell setprop log.tag.Counters WARN adb shell setprop persist.log.tag.ConnectivityManager INFO
adb shell setprop log.tag.CustomizedTextParser INFO adb shell setprop persist.log.tag.Counters WARN
adb shell setprop log.tag.EGL_emulation INFO adb shell setprop persist.log.tag.CustomizedTextParser INFO
adb shell setprop log.tag.HostConnection INFO adb shell setprop persist.log.tag.EGL_emulation INFO
adb shell setprop log.tag.InputMethodManager WARN adb shell setprop persist.log.tag.ffmpeg-kit-flutter INFO
adb shell setprop log.tag.InsetsSourceConsumer INFO adb shell setprop persist.log.tag.HostConnection INFO
adb shell setprop log.tag.InputTransport INFO adb shell setprop persist.log.tag.InputMethodManager WARN
adb shell setprop log.tag.J4A INFO adb shell setprop persist.log.tag.InsetsSourceConsumer INFO
adb shell setprop log.tag.MediaCodec WARN adb shell setprop persist.log.tag.InputTransport INFO
adb shell setprop log.tag.MediaMetadataRetriever INFO adb shell setprop persist.log.tag.J4A INFO
adb shell setprop log.tag.MediaMetadataRetrieverJNI INFO adb shell setprop persist.log.tag.MediaCodec WARN
adb shell setprop log.tag.NativeTiffDecoder INFO adb shell setprop persist.log.tag.MediaMetadataRetriever INFO
adb shell setprop log.tag.NuMediaExtractor INFO adb shell setprop persist.log.tag.MediaMetadataRetrieverJNI INFO
adb shell setprop log.tag.PipelineWatcher INFO adb shell setprop persist.log.tag.NativeTiffDecoder INFO
adb shell setprop log.tag.ReflectedParamUpdater INFO adb shell setprop persist.log.tag.NuMediaExtractor INFO
adb shell setprop log.tag.skia INFO adb shell setprop persist.log.tag.OpenGLRenderer INFO
adb shell setprop log.tag.SurfaceControl WARN adb shell setprop persist.log.tag.PipelineWatcher INFO
adb shell setprop log.tag.SurfaceUtils INFO adb shell setprop persist.log.tag.ReflectedParamUpdater INFO
adb shell setprop log.tag.SurfaceView WARN adb shell setprop persist.log.tag.skia INFO
adb shell setprop log.tag.VideoCapabilities ERROR adb shell setprop persist.log.tag.SurfaceControl WARN
adb shell setprop persist.log.tag.SurfaceUtils INFO
adb shell setprop persist.log.tag.SurfaceView WARN
adb shell setprop persist.log.tag.VideoCapabilities ERROR

File diff suppressed because one or more lines are too long

View file

@ -5,4 +5,10 @@ import 'package:test/fake.dart';
class FakeDeviceService extends Fake implements DeviceService { class FakeDeviceService extends Fake implements DeviceService {
@override @override
Future<int> getDefaultTimeZoneRawOffsetMillis() => SynchronousFuture(3600000); Future<int> getDefaultTimeZoneRawOffsetMillis() => SynchronousFuture(3600000);
@override
Future<int> getAvailableHeapSize() => SynchronousFuture(0x7fffffff);
@override
Future<void> requestGarbageCollection() => SynchronousFuture(null);
} }

View file

@ -26,6 +26,12 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
return []; return [];
} }
@override
Future<int?> getGeneration() async {
if (latency != null) await Future.delayed(latency!);
return 0;
}
@override @override
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries); Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries);

View file

@ -1889,15 +1889,6 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"de": [
"entryActionCast",
"overlayHistogramNone",
"castDialogTitle",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon"
],
"el": [ "el": [
"entryActionCast", "entryActionCast",
"castDialogTitle", "castDialogTitle",
@ -1916,37 +1907,19 @@
], ],
"fa": [ "fa": [
"filterLocatedLabel",
"filterTaggedLabel",
"filterRatingRejectedLabel",
"albumTierSpecial",
"coordinateFormatDms", "coordinateFormatDms",
"coordinateFormatDecimal",
"coordinateDms", "coordinateDms",
"coordinateDmsNorth", "coordinateDmsNorth",
"coordinateDmsSouth", "coordinateDmsSouth",
"coordinateDmsEast", "coordinateDmsEast",
"coordinateDmsWest", "coordinateDmsWest",
"overlayHistogramNone",
"overlayHistogramRGB", "overlayHistogramRGB",
"overlayHistogramLuminance",
"videoPlaybackSkip",
"viewerTransitionParallax",
"missingSystemFilePickerDialogMessage",
"unsupportedTypeDialogMessage", "unsupportedTypeDialogMessage",
"nameConflictDialogSingleSourceMessage",
"nameConflictDialogMultipleSourceMessage",
"addShortcutDialogLabel",
"addShortcutButtonLabel",
"noMatchingAppDialogMessage",
"binEntriesConfirmationDialogMessage", "binEntriesConfirmationDialogMessage",
"deleteEntriesConfirmationDialogMessage", "deleteEntriesConfirmationDialogMessage",
"moveUndatedConfirmationDialogMessage", "moveUndatedConfirmationDialogMessage",
"moveUndatedConfirmationDialogSetDate", "moveUndatedConfirmationDialogSetDate",
"videoResumeButtonLabel",
"setCoverDialogLatest", "setCoverDialogLatest",
"setCoverDialogAuto",
"setCoverDialogCustom",
"hideFilterConfirmationDialogMessage", "hideFilterConfirmationDialogMessage",
"newAlbumDialogTitle", "newAlbumDialogTitle",
"newAlbumDialogNameLabel", "newAlbumDialogNameLabel",
@ -4795,14 +4768,6 @@
"filePickerUseThisFolder" "filePickerUseThisFolder"
], ],
"id": [
"entryActionCast",
"castDialogTitle",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon"
],
"it": [ "it": [
"collectionActionSetHome", "collectionActionSetHome",
"setHomeCustomCollection", "setHomeCustomCollection",
@ -4810,18 +4775,13 @@
], ],
"ja": [ "ja": [
"columnCount",
"saveCopyButtonLabel",
"applyTooltip", "applyTooltip",
"chipActionShowCountryStates",
"chipActionCreateVault", "chipActionCreateVault",
"chipActionConfigureVault", "chipActionConfigureVault",
"entryActionCast",
"viewerActionLock", "viewerActionLock",
"viewerActionUnlock", "viewerActionUnlock",
"editorActionTransform", "editorActionTransform",
"editorTransformCrop", "editorTransformCrop",
"editorTransformRotate",
"cropAspectRatioFree", "cropAspectRatioFree",
"cropAspectRatioOriginal", "cropAspectRatioOriginal",
"cropAspectRatioSquare", "cropAspectRatioSquare",
@ -9274,24 +9234,5 @@
"filePickerOpenFrom", "filePickerOpenFrom",
"filePickerNoItems", "filePickerNoItems",
"filePickerUseThisFolder" "filePickerUseThisFolder"
],
"zh_Hant": [
"entryActionCast",
"overlayHistogramNone",
"overlayHistogramLuminance",
"castDialogTitle",
"aboutDataUsageSectionTitle",
"aboutDataUsageData",
"aboutDataUsageCache",
"aboutDataUsageDatabase",
"aboutDataUsageMisc",
"aboutDataUsageInternal",
"aboutDataUsageExternal",
"aboutDataUsageClearCache",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon",
"settingsViewerShowHistogram"
] ]
} }

View file

@ -1,3 +1,4 @@
In v1.10.5: In v1.10.6:
- enjoy the app in Catalan - detect HDR videos (but do not play them in their full HDR glory)
- removing animations also applies to pop up menus
Full changelog available on GitHub Full changelog available on GitHub