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.
runs-on: ubuntu-latest
steps:
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
@ -75,7 +75,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: appbundle
path: outputs/app-play-release.aab
@ -85,15 +85,15 @@ jobs:
needs: [ build ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get appbundle from artifacts.
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: appbundle
- name: Release app to beta channel.
uses: r0adkll/upload-google-play@v1.1.1
uses: r0adkll/upload-google-play@v1.1.3
with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
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="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
### Added

View file

@ -44,11 +44,22 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
workCont = cont
onStart()
}
dispose()
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() {
Log.i(LOG_TAG, "Start analysis worker")
Log.i(LOG_TAG, "Start analysis worker $id")
runBlocking {
FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
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() {
Log.i(LOG_TAG, "onStop")
super.onStop()

View file

@ -15,8 +15,12 @@ import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.drew.metadata.file.FileTypeDirectory
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.PixyMetaHelper
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap
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 deckers.thibault.aves.channel.calls.Coresult.Companion.safe
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.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -35,6 +35,8 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
"requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission)
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
"requestGarbageCollection" -> safe(call, result, ::requestGarbageCollection)
else -> result.notImplemented()
}
}
@ -123,6 +125,15 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
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 {
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.media.MediaScannerConnection
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.model.provider.MediaStoreImageProvider
import io.flutter.plugin.common.MethodCall
@ -20,13 +22,15 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
when (call.method) {
"checkObsoleteContentIds" -> ioScope.launch { safe(call, result, ::checkObsoleteContentIds) }
"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) }
else -> result.notImplemented()
}
}
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) {
result.error("checkObsoleteContentIds-args", "missing arguments", null)
return
@ -35,7 +39,7 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
}
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) {
result.error("checkObsoletePaths-args", "missing arguments", null)
return
@ -43,6 +47,25 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
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) {
val path = call.argument<String>("path")
val mimeType = call.argument<String>("mimeType")

View file

@ -1,6 +1,7 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import android.media.MediaFormat
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
@ -559,7 +560,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// identification of embedded gain map
if (xmpMeta.hasHdrGainMap()) {
flags = flags or MASK_HAS_HDR_GAIN_MAP
flags = flags or MASK_IS_HDR
}
} catch (e: XMPException) {
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
} catch (e: Exception) {
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_MULTIPAGE = 1 shl 4
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 = ";"
// 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 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()
result.success(untrackedPaths)
@ -150,7 +150,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
}
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()
result.success(untrackedPaths)

View file

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

View file

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

View file

@ -62,9 +62,21 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
}
init {
context.contentResolver.apply {
registerContentObserver(Settings.System.CONTENT_URI, true, contentObserver)
}
onAppResume()
}
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) {
@ -76,10 +88,6 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
Log.i(LOG_TAG, "onCancel arguments=$arguments")
}
fun dispose() {
context.contentResolver.unregisterContentObserver(contentObserver)
}
private fun success(settings: FieldMap) {
handler?.post {
try {

View file

@ -166,7 +166,7 @@ object Helper {
// 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
private val datePatterns = arrayOf(
private val dateFormats = arrayOf(
"yyyy:MM:dd HH:mm:ss",
"yyyy:MM:dd HH:mm",
"yyyy-MM-dd HH:mm:ss",
@ -179,7 +179,7 @@ object Helper {
"yyyy-MM",
"yyyyMMdd", // as used in IPTC data
"yyyy"
)
).map { SimpleDateFormat(it, Locale.ROOT) }.toTypedArray()
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 calendar: Calendar = GregorianCalendar()
@ -210,11 +210,10 @@ object Helper {
effectiveTimeZone = TimeZone.getTimeZone("GMT" + timeZoneMatcher.group().replace("Z".toRegex(), ""))
dateString = timeZoneMatcher.replaceAll("")
}
for (datePattern in datePatterns) {
for (dateFormat in dateFormats) {
try {
val parsed = SimpleDateFormat(datePattern, Locale.ROOT).apply {
this.timeZone = effectiveTimeZone ?: TimeZone.getTimeZone("GMT") // don't interpret zone time
}.parse(dateString)
dateFormat.timeZone = effectiveTimeZone ?: TimeZone.getTimeZone("GMT") // don't interpret zone time
val parsed = dateFormat.parse(dateString)
if (parsed != null) {
calendar.time = parsed
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.app.Activity
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.media.MediaScannerConnection
import android.net.Uri
@ -35,7 +39,7 @@ import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.io.SyncFailedException
import java.util.*
import java.util.Locale
import java.util.concurrent.CompletableFuture
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@ -45,11 +49,11 @@ import kotlin.coroutines.suspendCoroutine
class MediaStoreImageProvider : ImageProvider() {
fun fetchAll(
context: Context,
knownEntries: Map<Int?, Int?>,
knownEntries: Map<Long?, Int?>,
directory: String?,
handleNewEntry: NewEntryHandler,
) {
val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean {
val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
val knownDate = knownEntries[contentId]
return knownDate == null || knownDate < dateModifiedSecs
}
@ -89,7 +93,7 @@ class MediaStoreImageProvider : ImageProvider() {
var found = false
val fetched = arrayListOf<FieldMap>()
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) }
if (id != null) {
if (sourceMimeType == null || isImage(sourceMimeType)) {
@ -119,8 +123,8 @@ class MediaStoreImageProvider : ImageProvider() {
}
}
fun checkObsoleteContentIds(context: Context, knownContentIds: List<Int?>): List<Int> {
val foundContentIds = HashSet<Int>()
fun checkObsoleteContentIds(context: Context, knownContentIds: List<Long?>): List<Long> {
val foundContentIds = HashSet<Long>()
fun check(context: Context, contentUri: Uri) {
val projection = arrayOf(MediaStore.MediaColumns._ID)
try {
@ -128,7 +132,7 @@ class MediaStoreImageProvider : ImageProvider() {
if (cursor != null) {
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
while (cursor.moveToNext()) {
foundContentIds.add(cursor.getInt(idColumn))
foundContentIds.add(cursor.getLong(idColumn))
}
cursor.close()
}
@ -141,8 +145,8 @@ class MediaStoreImageProvider : ImageProvider() {
return knownContentIds.subtract(foundContentIds).filterNotNull().toList()
}
fun checkObsoletePaths(context: Context, knownPathById: Map<Int?, String?>): List<Int> {
val obsoleteIds = ArrayList<Int>()
fun checkObsoletePaths(context: Context, knownPathById: Map<Long?, String?>): List<Long> {
val obsoleteIds = ArrayList<Long>()
fun check(context: Context, contentUri: Uri) {
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
try {
@ -151,7 +155,7 @@ class MediaStoreImageProvider : ImageProvider() {
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
while (cursor.moveToNext()) {
val id = cursor.getInt(idColumn)
val id = cursor.getLong(idColumn)
val path = cursor.getString(pathColumn)
if (knownPathById.containsKey(id) && knownPathById[id] != path) {
obsoleteIds.add(id)
@ -168,6 +172,31 @@ class MediaStoreImageProvider : ImageProvider() {
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(
context: Context,
isValidEntry: NewEntryChecker,
@ -207,12 +236,12 @@ class MediaStoreImageProvider : ImageProvider() {
val needDuration = projection.contentEquals(VIDEO_PROJECTION)
while (cursor.moveToNext()) {
val contentId = cursor.getInt(idColumn)
val id = cursor.getLong(idColumn)
val dateModifiedSecs = cursor.getInt(dateModifiedColumn)
if (isValidEntry(contentId, dateModifiedSecs)) {
if (isValidEntry(id, dateModifiedSecs)) {
// for multiple items, `contentUri` is the root without 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)
// in that case we try to use the MIME type provided along the URI
val mimeType: String? = cursor.getString(mimeTypeColumn) ?: fileMimeType
@ -237,7 +266,7 @@ class MediaStoreImageProvider : ImageProvider() {
"sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
"durationMillis" to durationMillis,
// only for map export
"contentId" to contentId,
"contentId" to id,
)
if (MimeTypes.isHeic(mimeType)) {
@ -930,8 +959,10 @@ class MediaStoreImageProvider : ImageProvider() {
try {
val cursor = context.contentResolver.query(contentUri, projection, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
cursor.getColumnIndex(MediaStore.MediaColumns._ID).let {
if (it != -1) mediaContentUri = ContentUris.withAppendedId(contentUri, cursor.getLong(it))
val idColumn = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
if (idColumn != -1) {
val id = cursor.getLong(idColumn)
mediaContentUri = ContentUris.withAppendedId(contentUri, id)
}
cursor.close()
}
@ -994,4 +1025,4 @@ object MediaColumns {
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 {
byteSize ?: return true
val availableHeapSize = Runtime.getRuntime().let { it.maxMemory() - (it.totalMemory() - it.freeMemory()) }
val availableHeapSize = getAvailableHeapSize()
val danger = byteSize.toLong() > availableHeapSize
if (danger) {
Log.e(LOG_TAG, "trying to handle $byteSize bytes, with only $availableHeapSize free bytes")
}
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="app_widget_label">قاب عکس</string>
<string name="app_name">Aves</string>
<string name="safe_mode_shortcut_short_label">حالت امن</string>
</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": {},
"filterNoTitleLabel": "Без назвы",
"@filterNoTitleLabel": {},
"filterOnThisDayLabel": "У гэты дзень",
"filterOnThisDayLabel": "Ў гэты дзень",
"@filterOnThisDayLabel": {},
"filterRatingRejectedLabel": "Адхілена",
"@filterRatingRejectedLabel": {},
@ -263,7 +263,7 @@
"@nameConflictStrategyReplace": {},
"filterAspectRatioLandscapeLabel": "Ландшафтныя",
"@filterAspectRatioLandscapeLabel": {},
"filterBinLabel": "Кошык",
"filterBinLabel": "Сметніца",
"@filterBinLabel": {},
"filterFavouriteLabel": "Выбранае",
"@filterFavouriteLabel": {},
@ -551,7 +551,7 @@
"@mapPointNorthUpTooltip": {},
"viewerInfoLabelCoordinates": "Каардынаты",
"@viewerInfoLabelCoordinates": {},
"viewerInfoLabelOwner": "Уладальнік",
"viewerInfoLabelOwner": "Ўладальнік",
"@viewerInfoLabelOwner": {},
"viewerInfoLabelDuration": "Працягласць",
"@viewerInfoLabelDuration": {},
@ -625,7 +625,7 @@
"@mapZoomOutTooltip": {},
"openMapPageTooltip": "Паглядзець на старонцы карты",
"@openMapPageTooltip": {},
"mapEmptyRegion": "У гэтым рэгіёне няма малюнкаў",
"mapEmptyRegion": "Ў гэтым рэгіёне няма малюнкаў",
"@mapEmptyRegion": {},
"viewerInfoSearchEmpty": "Няма адпаведных ключоў",
"@viewerInfoSearchEmpty": {},
@ -683,7 +683,7 @@
"@setCoverDialogCustom": {},
"aboutBugCopyInfoInstruction": "Скапіяваць сістэмную інфармацыю",
"@aboutBugCopyInfoInstruction": {},
"vaultBinUsageDialogMessage": "Некаторыя сховішчы выкарыстоўваюць кошык.",
"vaultBinUsageDialogMessage": "Некаторыя сховішчы выкарыстоўваюць сметніцу.",
"@vaultBinUsageDialogMessage": {},
"aboutBugSaveLogInstruction": "Захаваць журналы праграмы ў файл",
"@aboutBugSaveLogInstruction": {},
@ -707,7 +707,7 @@
"@aboutBugSectionTitle": {},
"aboutBugCopyInfoButton": "Скапіяваць",
"@aboutBugCopyInfoButton": {},
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент у кошык?} few{Перамясціць гэтыя {count} элемента у кошык?} other{Перамясціць гэтыя {count} элементаў у кошык?}}",
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент ў сметніцу?} few{Перамясціць гэтыя {count} элемента ў сметніцу?} other{Перамясціць гэтыя {count} элементаў ў сметніцу?}}",
"@binEntriesConfirmationDialogMessage": {
"placeholders": {
"count": {}
@ -937,7 +937,7 @@
"@settingsActionImportDialogTitle": {},
"albumGroupTier": "Па ўзроўні",
"@albumGroupTier": {},
"drawerCollectionAll": "Уся калекцыя",
"drawerCollectionAll": "Ўся калекцыя",
"@drawerCollectionAll": {},
"sortByItemCount": "Па колькасці элементаў",
"@sortByItemCount": {},
@ -985,7 +985,7 @@
"@searchMetadataSectionTitle": {},
"settingsShowBottomNavigationBar": "Паказаць ніжнюю панэль навігацыі",
"@settingsShowBottomNavigationBar": {},
"collectionActionEmptyBin": "Ачысціць кошык",
"collectionActionEmptyBin": "Ачысціць сметніцу",
"@collectionActionEmptyBin": {},
"sortOrderAtoZ": "Ад А да Я",
"@sortOrderAtoZ": {},
@ -1127,7 +1127,7 @@
"@viewDialogLayoutSectionTitle": {},
"searchStatesSectionTitle": "Штаты",
"@searchStatesSectionTitle": {},
"dateThisMonth": "У гэтым месяцы",
"dateThisMonth": "Ў гэтым месяцы",
"@dateThisMonth": {},
"aboutPageTitle": "Пра нас",
"@aboutPageTitle": {},
@ -1165,7 +1165,7 @@
"@sortOrderHighestFirst": {},
"aboutLicensesAndroidLibrariesSectionTitle": "Бібліятэкі Android",
"@aboutLicensesAndroidLibrariesSectionTitle": {},
"binPageTitle": "Кошык",
"binPageTitle": "Сметніца",
"@binPageTitle": {},
"sortByAlbumFileName": "Па назве альбома і файла",
"@sortByAlbumFileName": {},
@ -1183,9 +1183,9 @@
"@settingsConfirmationBeforeDeleteItems": {},
"settingsConfirmationBeforeMoveUndatedItems": "Спытаць, перш чым перамяшчаць прадметы без даты",
"@settingsConfirmationBeforeMoveUndatedItems": {},
"settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў у кошык",
"settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў ў сметніцу",
"@settingsConfirmationAfterMoveToBinItems": {},
"settingsConfirmationBeforeMoveToBinItems": "Спытаць, перш чым перамяшчаць элементы ў кошык",
"settingsConfirmationBeforeMoveToBinItems": "Спытаць перад тым, як пераносіць элементы ў сметніцу",
"@settingsConfirmationBeforeMoveToBinItems": {},
"settingsNavigationDrawerAddAlbum": "Дадаць альбом",
"@settingsNavigationDrawerAddAlbum": {},
@ -1289,7 +1289,7 @@
"@settingsVideoGestureDoubleTapTogglePlay": {},
"addPathTooltip": "Дадаць шлях",
"@addPathTooltip": {},
"settingsEnableBin": "Выкарыстоўваць кошык",
"settingsEnableBin": "Выкарыстоўваць сметніцу",
"@settingsEnableBin": {},
"collectionMoveSuccessFeedback": "{count, plural, =1{Перамяшчаны 1 элемент} few{Перамяшчаны {count} элементы} other{Перамяшчаны {count} элементаў}}",
"@collectionMoveSuccessFeedback": {
@ -1377,7 +1377,7 @@
"@settingsViewerShowDescription": {},
"settingsViewerQuickActionEditorBanner": "Націсніце і ўтрымлівайце, каб перамяшчаць кнопкі і выбіраць дзеянні для адлюстравання ў праглядніку.",
"@settingsViewerQuickActionEditorBanner": {},
"settingsDisablingBinWarningDialogMessage": "Элементы ў кошыку будуць выдалены назаўжды.",
"settingsDisablingBinWarningDialogMessage": "Элементы ў сметніцы будуць выдалены назаўсёды.",
"@settingsDisablingBinWarningDialogMessage": {},
"settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Адлюстраваныя кнопкі",
"@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
@ -1521,7 +1521,7 @@
},
"collectionActionSetHome": "Ўсталяваць як галоўную",
"@collectionActionSetHome": {},
"setHomeCustomCollection": "Уласная калекцыя",
"setHomeCustomCollection": "Ўласная калекцыя",
"@setHomeCustomCollection": {},
"settingsThumbnailShowHdrIcon": "Паказаць значок HDR",
"@settingsThumbnailShowHdrIcon": {}

View file

@ -1354,5 +1354,17 @@
"aboutDataUsageClearCache": "Cache leeren",
"@aboutDataUsageClearCache": {},
"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": {},
"filterNoTagLabel": "بدون برچسب",
"@filterNoTagLabel": {},
"appName": "اِیوْز",
"appName": "اِیوز",
"@appName": {},
"entryActionRestore": "برگرداندن",
"@entryActionRestore": {},
@ -544,5 +544,41 @@
"wallpaperTargetHomeLock": "صفحهٔ خانه و صفحهٔ قفل",
"@wallpaperTargetHomeLock": {},
"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": {},
"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": {},
"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": {},
"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('luckris25', 'lk1thebestl@gmail.com'),
Contributor('Marc Amorós', 'marquitus99@gmail.com'),
Contributor('elea11', 'p.manuel.warnecke@gmail.com'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
@ -94,6 +95,7 @@ class Contributors {
// Contributor('امیر جهانگرد', 'ijahangard.a@gmail.com'), // Persian
// Contributor('slasb37', 'p84haghi@gmail.com'), // Persian
// Contributor('mimvahedi', 'vahedi0vahedi@gmail.com'), // Persian
// Contributor('Alireza Rashidi', 'alirezarashidigoorabi@gmail.com'), // Persian
// Contributor('Prasanta-Hembram', 'Prasantahembram720@gmail.com'), // Santali
// Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian
// Contributor('Shift18', 'bribable.lawyer@posteo.net'), // Swedish

View file

@ -242,6 +242,11 @@ class AvesEntry with AvesEntryBase {
return _bestDate;
}
@override
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
bool get isHdr => _catalogMetadata?.isHdr ?? false;
int get rating => _catalogMetadata?.rating ?? 0;
@override
@ -303,9 +308,6 @@ class AvesEntry with AvesEntryBase {
return d == null ? null : DateTime(d.year, d.month, d.day);
}
@override
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
@override
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/extensions/props.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/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart';
import 'package:flutter/foundation.dart';
extension ExtraAvesEntryCatalog on AvesEntry {
Future<void> catalog({required bool background, required bool force, required bool persist}) async {
if (isCatalogued && !force) return;
final beforeAvailableHeapSize = await deviceService.getAvailableHeapSize();
if (isSvg) {
// 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
@ -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 isHdr => catalogMetadata?.hasHdrGainMap ?? false;
String? getBurstKey(List<String> patterns) {
final key = BurstPatterns.getKeyForName(filenameWithoutExtension, patterns);
return key != null ? '$directory/$key' : null;

View file

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

View file

@ -1,5 +1,7 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/widgets.dart';
extension ExtraAccessibilityAnimations on AccessibilityAnimations {
bool get animate {
@ -14,4 +16,17 @@ extension ExtraAccessibilityAnimations on AccessibilityAnimations {
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)) {
// explicit GC before cataloguing multiple items
await deviceService.requestGarbageCollection();
await Future.forEach(entries, (entry) async {
await entry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist);
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(),
);
} else {
// explicit GC before cataloguing multiple items
await deviceService.requestGarbageCollection();
await catalogEntries(_analysisController, todoEntries);
updateDerivedFilters(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/vaults/vaults.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/debouncer.dart';
import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
class MediaStoreSource extends CollectionSource {
final Debouncer _changeDebouncer = Debouncer(delay: ADurations.mediaContentChangeDebounceDelay);
final Set<String> _changedUris = {};
int? _lastGeneration;
SourceInitializationState _initState = SourceInitializationState.none;
@override
@ -36,6 +41,7 @@ class MediaStoreSource extends CollectionSource {
_initState = directory != null ? SourceInitializationState.directory : SourceInitializationState.full;
}
addDirectories(albums: settings.pinnedFilters.whereType<AlbumFilter>().map((v) => v.album).toSet());
await updateGeneration();
unawaited(_loadEntries(
analysisController: analysisController,
directory: directory,
@ -305,6 +311,34 @@ class MediaStoreSource extends CollectionSource {
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
Future<void> _loadVaultEntries(String? directory) async {

View file

@ -17,6 +17,10 @@ abstract class DeviceService {
Future<bool> isSystemFilePickerEnabled();
Future<void> requestMediaManagePermission();
Future<int> getAvailableHeapSize();
Future<void> requestGarbageCollection();
}
class PlatformDeviceService implements DeviceService {
@ -104,4 +108,24 @@ class PlatformDeviceService implements DeviceService {
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<String>> getChangedUris(int sinceGeneration);
Future<int?> getGeneration();
// knownEntries: map of contentId -> dateModifiedSecs
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory});
@ -47,6 +51,29 @@ class PlatformMediaStoreService implements MediaStoreService {
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
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
try {

View file

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

View file

@ -2,12 +2,13 @@ import 'package:flutter/foundation.dart';
class ADurations {
// 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`
static const pageTransitionAnimation = Duration(milliseconds: 300 + 20); // ref `transitionDuration` used in `MaterialRouteTransitionMixin`
static const dialogTransitionAnimation = Duration(milliseconds: 150 + 20); // ref `transitionDuration` used in `DialogRoute`
static const drawerTransitionAnimation = Duration(milliseconds: 246 + 20); // ref `_kBaseSettleDuration` used in `DrawerControllerState`
static const toggleableTransitionAnimation = Duration(milliseconds: 200 + 20); // ref `_kToggleDuration` used in `ToggleableStateMixin`
static const pageTransitionAnimation = Duration(milliseconds: 300 + transitionMarginMillis); // ref `transitionDuration` used in `MaterialRouteTransitionMixin`
static const dialogTransitionAnimation = Duration(milliseconds: 150 + transitionMarginMillis); // ref `transitionDuration` used in `DialogRoute`
static const drawerTransitionAnimation = Duration(milliseconds: 246 + transitionMarginMillis); // ref `_kBaseSettleDuration` used in `DrawerControllerState`
static const toggleableTransitionAnimation = Duration(milliseconds: 200 + transitionMarginMillis); // ref `_kToggleDuration` used in `ToggleableStateMixin`
// common animations
static const sweeperOpacityAnimation = Duration(milliseconds: 150);
@ -16,6 +17,7 @@ class ADurations {
static const appBarTitleAnimation = Duration(milliseconds: 300);
static const appBarActionChangeAnimation = Duration(milliseconds: 200);
static const popupMenuAnimation = Duration(milliseconds: 300);
// filter grids animations
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/common/services.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/styles.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_page.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<bool> _shouldUseBoldFontLoader;
final TvRailController _tvRailController = TvRailController();
final CollectionSource _mediaStoreSource = MediaStoreSource();
final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: ADurations.mediaContentChangeDebounceDelay);
final Set<String> _changedUris = {};
final MediaStoreSource _mediaStoreSource = MediaStoreSource();
Size? _screenSize;
final ValueNotifier<PageTransitionsBuilder> _pageTransitionsBuilderNotifier = ValueNotifier(defaultPageTransitionsBuilder);
@ -184,7 +180,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
EquatableConfig.stringify = true;
_appSetup = _setup();
_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(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()));
_subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?)));
@ -399,6 +395,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
}
case AppLifecycleState.resumed:
RecentlyAddedFilter.updateNow();
_mediaStoreSource.checkForChanges();
break;
default:
break;
@ -614,21 +611,6 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
_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);
}

View file

@ -7,6 +7,7 @@ import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/trash.dart';
import 'package:aves/model/query.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/source/collection_lens.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),
);
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [
...quickActionButtons,
PopupMenuButton<EntrySetAction>(
@ -432,9 +434,10 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
},
onSelected: (action) async {
// 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);
},
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/text/change_highlight.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:flutter/material.dart';
import 'package:intl/intl.dart';
@ -202,7 +203,8 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final progressColor = colorScheme.primary;
final animate = context.select<Settings, bool>((v) => v.accessibilityAnimations.animate);
return PopScope(
@ -223,7 +225,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
width: diameter + 2,
height: diameter + 2,
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,
),
),

View file

@ -1,3 +1,4 @@
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:flutter/material.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 ColorScheme colorScheme = theme.colorScheme;
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 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/view/view.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:flutter/material.dart';
@ -77,7 +78,7 @@ class SourceStateSubtitle extends StatelessWidget {
const WidgetSpan(child: SizedBox(width: 8)),
TextSpan(
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.iconSize,
this.onMenuOpened,
super.popUpAnimationStyle,
});
@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';
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
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/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_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart';
@ -99,7 +100,7 @@ class _OverlayBackgroundState extends State<_OverlayBackground> {
}
BoxDecoration _buildBackgroundDecoration(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final isDark = Theme.of(context).isDark;
final gradientCenter = widget.gradientCenter;
return _initialized
? BoxDecoration(

View file

@ -1,4 +1,5 @@
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/providers/media_query_data_provider.dart';
import 'package:aves_utils/aves_utils.dart';
@ -104,7 +105,7 @@ class _OverlayBackgroundState extends State<_OverlayBackground> {
}
BoxDecoration _buildBackgroundDecoration(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final isDark = Theme.of(context).isDark;
return _initialized
? BoxDecoration(
color: isDark ? Colors.black87 : const Color(0xDDFFFFFF),

View file

@ -91,12 +91,12 @@ class GridThemeData {
if (located && showLocated) LocationIcon.located(),
if (!located && showUnlocated) LocationIcon.unlocated(),
if (entry.rating != 0 && showRating) RatingIcon(entry: entry),
if (entry.isHdr && showHdr) const HdrIcon(),
if (entry.isPureVideo)
VideoIcon(entry: entry)
else if (entry.isAnimated)
const AnimatedImageIcon()
else ...[
if (entry.isHdr && showHdr) const HdrIcon(),
if (entry.isRaw && showRaw) const RawIcon(),
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/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/view/view.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;
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
final actionDelegate = ChipActionDelegate();
final animations = context.read<Settings>().accessibilityAnimations;
final selectedAction = await showMenu<ChipAction>(
context: context,
position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size),
@ -149,10 +149,11 @@ class AvesFilterChip extends StatefulWidget {
);
}),
],
popUpAnimationStyle: animations.popUpAnimationStyle,
);
if (selectedAction != null) {
// 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);
}
}

View file

@ -6,6 +6,7 @@ import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/vaults/vaults.dart';
import 'package:aves/theme/icons.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_model/aves_model.dart';
import 'package:flutter/material.dart';
@ -295,7 +296,7 @@ class OverlayIcon extends StatelessWidget {
margin: margin,
padding: text != null ? EdgeInsetsDirectional.only(end: size / 4) : null,
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)),
),
child: text == null

View file

@ -1,5 +1,6 @@
import 'package:aves/model/settings/settings.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/borders.dart';
import 'package:flutter/material.dart';
@ -174,7 +175,7 @@ class OverlayTextButton extends StatelessWidget {
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred)),
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,
side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide(context)),
shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder(

View file

@ -2,6 +2,7 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.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_model/aves_model.dart';
import 'package:flutter/material.dart';
@ -27,7 +28,7 @@ class HighlightTitle extends StatelessWidget {
static List<Shadow> shadows(BuildContext context) => [
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),
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/path.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/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/popup/menu_row.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/settings.dart';
import 'package:aves/widgets/debug/storage.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
@ -36,6 +37,7 @@ class AppDebugPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return Directionality(
textDirection: TextDirection.ltr,
child: AvesScaffold(
@ -56,9 +58,10 @@ class AppDebugPage extends StatelessWidget {
.toList(),
onSelected: (action) async {
// 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));
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
),
],

View file

@ -2,8 +2,8 @@ import 'dart:math';
import 'package:aves/model/entry/entry.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/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/styles.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/identity/buttons/outlined_button.dart';
import 'package:aves/widgets/common/thumbnail/decorated.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class RenameEntrySetPage extends StatefulWidget {
static const routeName = '/rename_entry_set';
@ -62,6 +64,7 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
final l10n = context.l10n;
final textScaler = MediaQuery.textScalerOf(context);
final effectiveThumbnailExtent = max(thumbnailExtent, textScaler.scale(thumbnailExtent));
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return AvesScaffold(
appBar: AppBar(
title: Text(l10n.renameEntrySetPageTitle),
@ -104,11 +107,12 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
},
onSelected: (key) async {
// 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);
},
tooltip: l10n.renameEntrySetPageInsertTooltip,
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/theme/icons.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/thumbnail/image.dart';
import 'package:flutter/material.dart';
@ -50,7 +51,7 @@ class ItemPicker extends StatelessWidget {
bottom: -1,
child: Container(
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),
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/filters.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/source/album.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 void Function(ChipSetAction action) onActionSelected,
}) {
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [
if (widget.moveType != null)
..._quickActions.where(isVisible).map(
@ -227,9 +229,10 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
FocusManager.instance.primaryFocus?.unfocus();
// 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);
},
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/query.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/source/collection_source.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)),
);
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return [
...quickActionButtons,
PopupMenuButton<ChipSetAction>(
@ -366,9 +368,10 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
FocusManager.instance.primaryFocus?.unfocus();
// 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);
},
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/geotiff.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
@ -462,6 +463,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
) async {
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
final animations = context.read<Settings>().accessibilityAnimations;
final selectedAction = await showMenu<MapClusterAction>(
context: context,
position: RelativeRect.fromRect(tapLocalPosition & touchArea, Offset.zero & overlay.size),
@ -482,10 +484,11 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
MapClusterAction.removeLocation,
].map(_buildMenuItem),
],
popUpAnimationStyle: animations.popUpAnimationStyle,
);
if (selectedAction != null) {
// 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();
switch (selectedAction) {
case MapClusterAction.editLocation:

View file

@ -1,5 +1,6 @@
import 'package:aves/theme/durations.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:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart';
@ -16,7 +17,8 @@ class SettingsTileLeading extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return AnimatedContainer(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
@ -32,7 +34,7 @@ class SettingsTileLeading extends StatelessWidget {
icon,
size: 18,
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 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
@ -18,6 +19,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class FilePickerPage extends StatefulWidget {
static const routeName = '/file_picker';
@ -57,6 +59,7 @@ class _FilePickerPageState extends State<FilePickerPage> {
return !isHidden;
}
}).toList();
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return PopScope(
canPop: _directory.relativeDir.isEmpty,
onPopInvoked: (didPop) {
@ -82,13 +85,14 @@ class _FilePickerPageState extends State<FilePickerPage> {
},
onSelected: (action) async {
// 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) {
case _PickerAction.toggleHiddenView:
settings.filePickerShowHiddenFiles = !showHidden;
setState(() {});
}
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
),
],

View file

@ -1,10 +1,11 @@
import 'dart:convert';
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/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
@ -45,6 +46,7 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> with FeedbackMi
@override
Widget build(BuildContext context) {
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return AvesScaffold(
appBar: AppBar(
title: InteractiveAppBarTitle(
@ -72,9 +74,10 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> with FeedbackMi
},
onSelected: (action) async {
// 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);
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
].map((v) => FontSizeIconTheme(child: v)).toList(),
),

View file

@ -1,5 +1,6 @@
import 'package:aves/theme/styles.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:intl/intl.dart';
@ -20,7 +21,7 @@ class LinearPercentIndicatorText extends StatelessWidget {
TextSpan(
text: percentFormat.format(percent),
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 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/multipage.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.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/theme/durations.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/controls/controller.dart';
import 'package:aves/widgets/viewer/controls/intents.dart';
@ -171,7 +172,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
return ValueListenableBuilder<double>(
valueListenable: widget.overlayOpacity,
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(
color: background.withOpacity(backgroundOpacity),
child: child,

View file

@ -1,6 +1,7 @@
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/source/collection_lens.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/entry_viewer_stack.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
@ -26,6 +27,8 @@ class EntryViewerPage extends StatefulWidget {
static EdgeInsets snackBarMargin(BuildContext 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> {
@ -56,11 +59,7 @@ class _EntryViewerPageState extends State<EntryViewerPage> {
viewerController: _viewerController,
),
),
backgroundColor: Navigator.canPop(context)
? Colors.transparent
: Theme.of(context).brightness == Brightness.dark
? Colors.black
: Colors.white,
backgroundColor: Navigator.canPop(context) ? Colors.transparent : EntryViewerPage.getBackground(context),
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/extensions/props.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/source/collection_lens.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/view/view.dart';
@ -49,6 +49,7 @@ class InfoAppBar extends StatelessWidget {
final commonActions = EntryActions.commonMetadataActions.where(isVisible);
final formatSpecificActions = EntryActions.formatSpecificMetadataActions.where(isVisible);
final useTvLayout = settings.useTvLayout;
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return SliverAppBar(
leading: useTvLayout
? null
@ -91,9 +92,10 @@ class InfoAppBar extends StatelessWidget {
],
onSelected: (action) async {
// 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);
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
].map((v) => FontSizeIconTheme(child: v)).toList(),
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/theme/durations.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/overlay/details/date.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 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({
super.key,

View file

@ -5,6 +5,7 @@ import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/styles.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/borders.dart';
import 'package:aves_video/aves_video.dart';
@ -39,9 +40,9 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
@override
Widget build(BuildContext context) {
final blurred = settings.enableBlurEffect;
final brightness = Theme.of(context).brightness;
final theme = Theme.of(context);
final textStyle = TextStyle(
shadows: brightness == Brightness.dark ? AStyles.embossShadows : null,
shadows: theme.isDark ? AStyles.embossShadows : null,
);
const strutStyle = StrutStyle(
forceStrutHeight: true,
@ -71,7 +72,7 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
decoration: BoxDecoration(
color: Themes.overlayBackgroundColor(brightness: brightness, blurred: blurred),
color: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
border: AvesBorder.border(context),
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/extensions/multipage.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/source/collection_lens.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/view/view.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 videoActions = widget.videoActions;
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?>(
selector: (context, vc) => vc.getController(pageEntry),
builder: (context, videoController, child) {
@ -301,10 +302,11 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
]
];
},
onSelected: (action) {
onSelected: (action) async {
_popupExpandedNotifier.value = null;
// 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: () {
_popupExpandedNotifier.value = null;
@ -316,6 +318,7 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
// so we make sure overlay stays visible
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/widgets/common/extensions/build_context.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/material.dart';
@ -40,7 +41,7 @@ class _ErrorViewState extends State<ErrorView> {
// use container to expand constraints, so that the user can tap anywhere
child: Container(
// opaque to cover potential lower quality layer below
color: Colors.black,
color: EntryViewerPage.getBackground(context),
child: FutureBuilder<bool>(
future: _exists,
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/common/basic/insets.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/controls/controller.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
@ -50,7 +51,7 @@ class WallpaperPage extends StatelessWidget {
),
)
: const SizedBox(),
backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white,
backgroundColor: Theme.of(context).isDark ? Colors.black : Colors.white,
resizeToAvoidBottomInset: false,
);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -53,7 +53,7 @@ class EntryGoogleMap<T> extends StatefulWidget {
State<StatefulWidget> createState() => _EntryGoogleMapState<T>();
}
class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindingObserver {
class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> {
GoogleMapController? _serviceMapController;
final List<StreamSubscription> _subscriptions = [];
Map<MarkerKey<T>, GeoEntry<T>> _geoEntryByMarkerKey = {};
@ -72,7 +72,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_sizeNotifier.addListener(_onSizeChanged);
_registerWidget(widget);
}
@ -88,7 +87,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
void dispose() {
_unregisterWidget(widget);
_serviceMapController?.dispose();
WidgetsBinding.instance.removeObserver(this);
_sizeNotifier.dispose();
super.dispose();
}
@ -109,15 +107,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
..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
Widget build(BuildContext context) {
return Stack(

View file

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

View file

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

View file

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

View file

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

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "737321f9be522620ed3794937298fb0027a48a402624fa2500f7532f94aea810"
sha256: "4eec93681221723a686ad580c2e7d960e1017cf1a4e0a263c2573c2c6b0bf5cd"
url: "https://pub.dev"
source: hosted
version: "1.3.22"
version: "1.3.25"
analyzer:
dependency: transitive
description:
@ -314,10 +314,10 @@ packages:
dependency: "direct main"
description:
name: dynamic_color
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://pub.dev"
source: hosted
version: "1.6.9"
version: "1.7.0"
equatable:
dependency: "direct main"
description:
@ -388,10 +388,10 @@ packages:
dependency: transitive
description:
name: firebase_core
sha256: "7e049e32a9d347616edb39542cf92cd53fdb4a99fb6af0a0bff327c14cd76445"
sha256: "53316975310c8af75a96e365f9fccb67d1c544ef0acdbf0d88bbe30eedd1c4f9"
url: "https://pub.dev"
source: hosted
version: "2.25.4"
version: "2.27.0"
firebase_core_platform_interface:
dependency: transitive
description:
@ -404,26 +404,26 @@ packages:
dependency: transitive
description:
name: firebase_core_web
sha256: "57e61d6010e253b36d38191cefd6199d7849152cdcd234b61ca290cdb278a0ba"
sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492
url: "https://pub.dev"
source: hosted
version: "2.11.4"
version: "2.11.5"
firebase_crashlytics:
dependency: transitive
description:
name: firebase_crashlytics
sha256: "4c754db28a7daabe03c4cbf1079dbe81e6f0681284fed6d07e0e640b7f1027c4"
sha256: c4f1b723d417bc9c4774810e774ff91df8fb0032d33fb2888b2c887e865581b8
url: "https://pub.dev"
source: hosted
version: "3.4.15"
version: "3.4.18"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: "08c5d7b5f93dbad7306d26702935abd8b579313ea256eb27006562a1867df249"
sha256: c5a11fca3df76a98e3fa68fde8b10a08aacb9a7639f619fbfd4dad6c67a08643
url: "https://pub.dev"
source: hosted
version: "3.6.22"
version: "3.6.25"
fixnum:
dependency: transitive
description:
@ -436,10 +436,10 @@ packages:
dependency: "direct main"
description:
name: flex_color_picker
sha256: "0871edc170153cfc3de316d30625f40a85daecfa76ce541641f3cc0ec7757cbf"
sha256: "904373c7b0531fd4a92d29705a80ab4594b7647da2d93044487aaec4614cb6ed"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
version: "3.4.0"
flex_seed_scheme:
dependency: transitive
description:
@ -452,10 +452,10 @@ packages:
dependency: "direct main"
description:
name: floating
sha256: d9d563089e34fbd714ffdcdd2df447ec41b40c9226dacae6b4f78847aef8b991
sha256: "04c3c96909b94dd6d2d121c69707739825e1f3dceca5ae451a9b8c0e652d246b"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
fluster:
dependency: "direct main"
description:
@ -532,10 +532,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: a64c5323ac83ed2b7940d2b6288d160aa1753ff271ba9d9b2a86770414aa3eab
sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db
url: "https://pub.dev"
source: hosted
version: "0.6.20+1"
version: "0.6.21"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -635,26 +635,26 @@ packages:
dependency: transitive
description:
name: google_maps_flutter_android
sha256: "714530f865f13bb3b9505c58821c3baed5d247a871724acf5d2ea5808fbed02c"
sha256: "256b3c974e415bd17555ceff76a5d0badd2cbfd29febfc23070993358f639550"
url: "https://pub.dev"
source: hosted
version: "2.6.2"
version: "2.7.0"
google_maps_flutter_ios:
dependency: transitive
description:
name: google_maps_flutter_ios
sha256: "29503b5159da2308a66212c3827963998bfb943ba073e2114fb2d486b47fd2c8"
sha256: "0997f99d8bd8712f648a49bfc96a3cf2713cfdaf73a005c719aab74eaef94030"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.5.0"
google_maps_flutter_platform_interface:
dependency: transitive
description:
name: google_maps_flutter_platform_interface
sha256: "6060779f020638a8eedeb0fb14234818e5fa32ec45a4653d6428ab436e2bbc64"
sha256: "167af879da4d004cd58771f1469b91dcc3b9b0a2c5334cc6bf71fd41d4b35403"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
version: "2.6.0"
google_maps_flutter_web:
dependency: transitive
description:
@ -835,10 +835,10 @@ packages:
dependency: transitive
description:
name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770
url: "https://pub.dev"
source: hosted
version: "2.0.2+1"
version: "2.1.0"
logging:
dependency: transitive
description:
@ -851,10 +851,10 @@ packages:
dependency: transitive
description:
name: markdown
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90"
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
url: "https://pub.dev"
source: hosted
version: "7.2.1"
version: "7.2.2"
matcher:
dependency: transitive
description:
@ -1237,10 +1237,10 @@ packages:
dependency: "direct main"
description:
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.1.2"
pub_semver:
dependency: transitive
description:
@ -1619,10 +1619,10 @@ packages:
dependency: transitive
description:
name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev"
source: hosted
version: "6.2.4"
version: "6.2.5"
url_launcher_linux:
dependency: transitive
description:
@ -1755,10 +1755,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.3.0"
win32_registry:
dependency: transitive
description:
@ -1801,4 +1801,4 @@ packages:
version: "3.1.2"
sdks:
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
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
version: 1.10.5+114
version: 1.10.6+115
publish_to: none
environment:
# this project bundles Flutter SDK via `flutter_wrapper`
# cf https://github.com/passsy/flutter_wrapper
flutter: 3.19.1
flutter: 3.19.3
sdk: '>=3.3.0 <4.0.0'
# 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
adb.exe shell setprop log.tag.ACodec WARN
adb.exe shell setprop log.tag.AHierarchicalStateMachine ERROR
adb.exe shell setprop log.tag.AudioCapabilities ERROR
adb.exe shell setprop log.tag.AudioTrack INFO
adb.exe shell setprop log.tag.BufferPoolAccessor2.0 INFO
adb.exe shell setprop log.tag.CCodec INFO
adb.exe shell setprop log.tag.CCodecBufferChannel INFO
adb.exe shell setprop log.tag.CCodecBuffers INFO
adb.exe shell setprop log.tag.CCodecConfig INFO
adb.exe shell setprop log.tag.Codec2Client INFO
adb.exe shell setprop log.tag.CompatibilityChangeReporter INFO
adb.exe shell setprop log.tag.Counters WARN
adb.exe shell setprop log.tag.CustomizedTextParser INFO
adb.exe shell setprop log.tag.EGL_emulation INFO
adb.exe shell setprop log.tag.HostConnection INFO
adb.exe shell setprop log.tag.InputMethodManager WARN
adb.exe shell setprop log.tag.InsetsSourceConsumer INFO
adb.exe shell setprop log.tag.InputTransport INFO
adb.exe shell setprop log.tag.J4A INFO
adb.exe shell setprop log.tag.MediaCodec WARN
adb.exe shell setprop log.tag.MediaMetadataRetriever INFO
adb.exe shell setprop log.tag.MediaMetadataRetrieverJNI INFO
adb.exe shell setprop log.tag.NativeTiffDecoder INFO
adb.exe shell setprop log.tag.NuMediaExtractor INFO
adb.exe shell setprop log.tag.PipelineWatcher INFO
adb.exe shell setprop log.tag.ReflectedParamUpdater INFO
adb.exe shell setprop log.tag.skia INFO
adb.exe shell setprop log.tag.SurfaceControl WARN
adb.exe shell setprop log.tag.SurfaceUtils INFO
adb.exe shell setprop log.tag.SurfaceView WARN
adb.exe shell setprop log.tag.VideoCapabilities ERROR
adb.exe shell setprop persist.log.tag.ACodec WARN
adb.exe shell setprop persist.log.tag.AHierarchicalStateMachine ERROR
adb.exe shell setprop persist.log.tag.AudioCapabilities ERROR
adb.exe shell setprop persist.log.tag.AudioTrack INFO
adb.exe shell setprop persist.log.tag.BufferPoolAccessor2.0 INFO
adb.exe shell setprop persist.log.tag.CCodec INFO
adb.exe shell setprop persist.log.tag.CCodecBufferChannel INFO
adb.exe shell setprop persist.log.tag.CCodecBuffers INFO
adb.exe shell setprop persist.log.tag.CCodecConfig INFO
adb.exe shell setprop persist.log.tag.Codec2Client INFO
adb.exe shell setprop persist.log.tag.CompatibilityChangeReporter INFO
adb.exe shell setprop persist.log.tag.Counters WARN
adb.exe shell setprop persist.log.tag.CustomizedTextParser INFO
adb.exe shell setprop persist.log.tag.EGL_emulation INFO
adb.exe shell setprop persist.log.tag.HostConnection INFO
adb.exe shell setprop persist.log.tag.InputMethodManager WARN
adb.exe shell setprop persist.log.tag.InsetsSourceConsumer INFO
adb.exe shell setprop persist.log.tag.InputTransport INFO
adb.exe shell setprop persist.log.tag.J4A INFO
adb.exe shell setprop persist.log.tag.MediaCodec WARN
adb.exe shell setprop persist.log.tag.MediaMetadataRetriever INFO
adb.exe shell setprop persist.log.tag.MediaMetadataRetrieverJNI INFO
adb.exe shell setprop persist.log.tag.NativeTiffDecoder INFO
adb.exe shell setprop persist.log.tag.NuMediaExtractor INFO
adb.exe shell setprop persist.log.tag.PipelineWatcher INFO
adb.exe shell setprop persist.log.tag.ReflectedParamUpdater INFO
adb.exe shell setprop persist.log.tag.skia INFO
adb.exe shell setprop persist.log.tag.SurfaceControl WARN
adb.exe shell setprop persist.log.tag.SurfaceUtils INFO
adb.exe shell setprop persist.log.tag.SurfaceView WARN
adb.exe shell setprop persist.log.tag.VideoCapabilities ERROR
@echo off
endlocal

View file

@ -1,32 +1,36 @@
#!/bin/bash
adb shell setprop log.tag.ACodec WARN
adb shell setprop log.tag.AHierarchicalStateMachine ERROR
adb shell setprop log.tag.AudioCapabilities ERROR
adb shell setprop log.tag.AudioTrack INFO
adb shell setprop log.tag.BufferPoolAccessor2.0 INFO
adb shell setprop log.tag.CCodec INFO
adb shell setprop log.tag.CCodecBufferChannel INFO
adb shell setprop log.tag.CCodecBuffers INFO
adb shell setprop log.tag.CCodecConfig INFO
adb shell setprop log.tag.Codec2Client INFO
adb shell setprop log.tag.CompatibilityChangeReporter INFO
adb shell setprop log.tag.Counters WARN
adb shell setprop log.tag.CustomizedTextParser INFO
adb shell setprop log.tag.EGL_emulation INFO
adb shell setprop log.tag.HostConnection INFO
adb shell setprop log.tag.InputMethodManager WARN
adb shell setprop log.tag.InsetsSourceConsumer INFO
adb shell setprop log.tag.InputTransport INFO
adb shell setprop log.tag.J4A INFO
adb shell setprop log.tag.MediaCodec WARN
adb shell setprop log.tag.MediaMetadataRetriever INFO
adb shell setprop log.tag.MediaMetadataRetrieverJNI INFO
adb shell setprop log.tag.NativeTiffDecoder INFO
adb shell setprop log.tag.NuMediaExtractor INFO
adb shell setprop log.tag.PipelineWatcher INFO
adb shell setprop log.tag.ReflectedParamUpdater INFO
adb shell setprop log.tag.skia INFO
adb shell setprop log.tag.SurfaceControl WARN
adb shell setprop log.tag.SurfaceUtils INFO
adb shell setprop log.tag.SurfaceView WARN
adb shell setprop log.tag.VideoCapabilities ERROR
adb shell setprop persist.log.tag.ACodec WARN
adb shell setprop persist.log.tag.AHierarchicalStateMachine ERROR
adb shell setprop persist.log.tag.AudioCapabilities ERROR
adb shell setprop persist.log.tag.AudioTrack INFO
adb shell setprop persist.log.tag.BufferPoolAccessor2.0 INFO
adb shell setprop persist.log.tag.CCodec INFO
adb shell setprop persist.log.tag.CCodecBufferChannel INFO
adb shell setprop persist.log.tag.CCodecBuffers INFO
adb shell setprop persist.log.tag.CCodecConfig INFO
adb shell setprop persist.log.tag.Codec2Client INFO
adb shell setprop persist.log.tag.CompatibilityChangeReporter INFO
adb shell setprop persist.log.tag.ConnectivityManager INFO
adb shell setprop persist.log.tag.Counters WARN
adb shell setprop persist.log.tag.CustomizedTextParser INFO
adb shell setprop persist.log.tag.EGL_emulation INFO
adb shell setprop persist.log.tag.ffmpeg-kit-flutter INFO
adb shell setprop persist.log.tag.HostConnection INFO
adb shell setprop persist.log.tag.InputMethodManager WARN
adb shell setprop persist.log.tag.InsetsSourceConsumer INFO
adb shell setprop persist.log.tag.InputTransport INFO
adb shell setprop persist.log.tag.J4A INFO
adb shell setprop persist.log.tag.MediaCodec WARN
adb shell setprop persist.log.tag.MediaMetadataRetriever INFO
adb shell setprop persist.log.tag.MediaMetadataRetrieverJNI INFO
adb shell setprop persist.log.tag.NativeTiffDecoder INFO
adb shell setprop persist.log.tag.NuMediaExtractor INFO
adb shell setprop persist.log.tag.OpenGLRenderer INFO
adb shell setprop persist.log.tag.PipelineWatcher INFO
adb shell setprop persist.log.tag.ReflectedParamUpdater INFO
adb shell setprop persist.log.tag.skia INFO
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 {
@override
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 [];
}
@override
Future<int?> getGeneration() async {
if (latency != null) await Future.delayed(latency!);
return 0;
}
@override
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries);

View file

@ -1889,15 +1889,6 @@
"filePickerUseThisFolder"
],
"de": [
"entryActionCast",
"overlayHistogramNone",
"castDialogTitle",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon"
],
"el": [
"entryActionCast",
"castDialogTitle",
@ -1916,37 +1907,19 @@
],
"fa": [
"filterLocatedLabel",
"filterTaggedLabel",
"filterRatingRejectedLabel",
"albumTierSpecial",
"coordinateFormatDms",
"coordinateFormatDecimal",
"coordinateDms",
"coordinateDmsNorth",
"coordinateDmsSouth",
"coordinateDmsEast",
"coordinateDmsWest",
"overlayHistogramNone",
"overlayHistogramRGB",
"overlayHistogramLuminance",
"videoPlaybackSkip",
"viewerTransitionParallax",
"missingSystemFilePickerDialogMessage",
"unsupportedTypeDialogMessage",
"nameConflictDialogSingleSourceMessage",
"nameConflictDialogMultipleSourceMessage",
"addShortcutDialogLabel",
"addShortcutButtonLabel",
"noMatchingAppDialogMessage",
"binEntriesConfirmationDialogMessage",
"deleteEntriesConfirmationDialogMessage",
"moveUndatedConfirmationDialogMessage",
"moveUndatedConfirmationDialogSetDate",
"videoResumeButtonLabel",
"setCoverDialogLatest",
"setCoverDialogAuto",
"setCoverDialogCustom",
"hideFilterConfirmationDialogMessage",
"newAlbumDialogTitle",
"newAlbumDialogNameLabel",
@ -4795,14 +4768,6 @@
"filePickerUseThisFolder"
],
"id": [
"entryActionCast",
"castDialogTitle",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon"
],
"it": [
"collectionActionSetHome",
"setHomeCustomCollection",
@ -4810,18 +4775,13 @@
],
"ja": [
"columnCount",
"saveCopyButtonLabel",
"applyTooltip",
"chipActionShowCountryStates",
"chipActionCreateVault",
"chipActionConfigureVault",
"entryActionCast",
"viewerActionLock",
"viewerActionUnlock",
"editorActionTransform",
"editorTransformCrop",
"editorTransformRotate",
"cropAspectRatioFree",
"cropAspectRatioOriginal",
"cropAspectRatioSquare",
@ -9274,24 +9234,5 @@
"filePickerOpenFrom",
"filePickerNoItems",
"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:
- enjoy the app in Catalan
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