Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2025-04-06 23:13:41 +02:00
commit 4dd77483cd
58 changed files with 2775 additions and 951 deletions

View file

@ -17,11 +17,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with: with:
egress-policy: audit egress-policy: audit
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # v4.6.0

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with: with:
egress-policy: audit egress-policy: audit
@ -52,7 +52,7 @@ jobs:
build-mode: manual build-mode: manual
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with: with:
egress-policy: audit egress-policy: audit

View file

@ -18,7 +18,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with: with:
egress-policy: audit egress-policy: audit
@ -43,20 +43,15 @@ jobs:
# `KEY_JKS` should contain the result of: # `KEY_JKS` should contain the result of:
# gpg -c --armor keystore.jks # gpg -c --armor keystore.jks
# `KEY_JKS_PASSPHRASE` should contain the passphrase used for the command above # `KEY_JKS_PASSPHRASE` should contain the passphrase used for the command above
# The SkSL bundle must be produced with the same Flutter engine as the one used to build the artifact
# flutter build <subcommand> --bundle-sksl-path shaders.sksl.json
# do not bundle shaders for izzy/libre flavours, to avoid crashes in some environments:
# cf https://github.com/deckerst/aves/issues/388
# cf https://github.com/deckerst/aves/issues/398
run: | run: |
echo "${{ secrets.KEY_JKS }}" > release.keystore.asc echo "${{ secrets.KEY_JKS }}" > release.keystore.asc
gpg -d --passphrase "${{ secrets.KEY_JKS_PASSPHRASE }}" --batch release.keystore.asc > $AVES_STORE_FILE gpg -d --passphrase "${{ secrets.KEY_JKS_PASSPHRASE }}" --batch release.keystore.asc > $AVES_STORE_FILE
rm release.keystore.asc rm release.keystore.asc
mkdir outputs mkdir outputs
scripts/apply_flavor_play.sh scripts/apply_flavor_play.sh
./flutterw build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders.sksl.json ./flutterw build appbundle -t lib/main_play.dart --flavor play
cp build/app/outputs/bundle/playRelease/*.aab outputs cp build/app/outputs/bundle/playRelease/*.aab outputs
./flutterw build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders.sksl.json ./flutterw build apk -t lib/main_play.dart --flavor play
cp build/app/outputs/apk/play/release/*.apk outputs cp build/app/outputs/apk/play/release/*.apk outputs
scripts/apply_flavor_izzy.sh scripts/apply_flavor_izzy.sh
./flutterw build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi ./flutterw build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi
@ -98,7 +93,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with: with:
egress-policy: audit egress-policy: audit

View file

@ -31,7 +31,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with: with:
egress-policy: audit egress-policy: audit

View file

@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased] ## <a id="unreleased"></a>[Unreleased]
## <a id="v1.12.9"></a>[v1.12.9] - 2025-04-06
### Added
- Kannada translation (thanks Chethan, Prasannakumar T Bhat)
### Changed
- enable Impeller rendering engine
### Fixed
- memory pressure during browsing
## <a id="v1.12.8"></a>[v1.12.8] - 2025-03-25 ## <a id="v1.12.8"></a>[v1.12.8] - 2025-03-25
### Fixed ### Fixed

View file

@ -329,12 +329,8 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<!--
Screenshot driver scenario is not supported by Impeller: "Compressed screenshots not supported for Impeller".
As of Flutter v3.29.2, switching pages with alpha transition yields artifacts when Impeller is enabled.
-->
<meta-data <meta-data
android:name="io.flutter.embedding.android.EnableImpeller" android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" /> android:value="true" />
</application> </application>
</manifest> </manifest>

View file

@ -1,6 +1,8 @@
package deckers.thibault.aves.channel.calls package deckers.thibault.aves.channel.calls
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.core.net.toUri import androidx.core.net.toUri
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
@ -21,7 +23,8 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) { when (call.method) {
"getEntry" -> ioScope.launch { safe(call, result, ::getEntry) } "getEntry" -> ioScope.launch { safe(call, result, ::getEntry) }
"clearSizedThumbnailDiskCache" -> ioScope.launch { safe(call, result, ::clearSizedThumbnailDiskCache) } "clearImageDiskCache" -> ioScope.launch { safe(call, result, ::clearImageDiskCache) }
"clearImageMemoryCache" -> ioScope.launch { safe(call, result, ::clearImageMemoryCache) }
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
@ -47,11 +50,18 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
}) })
} }
private fun clearSizedThumbnailDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { private fun clearImageDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Glide.get(context).clearDiskCache() Glide.get(context).clearDiskCache()
result.success(null) result.success(null)
} }
private fun clearImageMemoryCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Handler(Looper.getMainLooper()).post {
Glide.get(context).clearMemory()
}
result.success(null)
}
companion object { companion object {
const val CHANNEL = "deckers.thibault/aves/media_fetch_object" const val CHANNEL = "deckers.thibault/aves/media_fetch_object"
} }

View file

@ -29,10 +29,6 @@ import kotlin.math.roundToInt
class RegionFetcher internal constructor( class RegionFetcher internal constructor(
private val context: Context, private val context: Context,
) { ) {
private var lastDecoderRef: LastDecoderRef? = null
private val exportUris = HashMap<Pair<Uri, Int?>, Uri>()
// return decoded bytes in ARGB_8888, with trailer bytes: // return decoded bytes in ARGB_8888, with trailer bytes:
// - width (int32) // - width (int32)
// - height (int32) // - height (int32)
@ -63,24 +59,12 @@ class RegionFetcher internal constructor(
return return
} }
var currentDecoderRef = lastDecoderRef
if (currentDecoderRef != null && currentDecoderRef.requestKey != requestKey) {
currentDecoderRef = null
}
try { try {
if (currentDecoderRef == null) { val decoder = getOrCreateDecoder(uri, requestKey)
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input -> if (decoder == null) {
BitmapRegionDecoderCompat.newInstance(input) result.error("fetch-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null)
} return
if (newDecoder == null) {
result.error("fetch-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null)
return
}
currentDecoderRef = LastDecoderRef(requestKey, newDecoder)
} }
val decoder = currentDecoderRef.decoder
lastDecoderRef = currentDecoderRef
// with raw images, the known image size may not match the decoded image size // with raw images, the known image size may not match the decoded image size
// so we scale the requested region accordingly // so we scale the requested region accordingly
@ -159,6 +143,26 @@ class RegionFetcher internal constructor(
} }
} }
private fun getOrCreateDecoder(uri: Uri, requestKey: Pair<Uri, Int?>): BitmapRegionDecoder? {
var decoderRef = decoderPool.firstOrNull { it.requestKey == requestKey }
if (decoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
BitmapRegionDecoderCompat.newInstance(input)
}
if (newDecoder == null) {
return null
}
decoderRef = DecoderRef(requestKey, newDecoder)
} else {
decoderPool.remove(decoderRef)
}
decoderPool.add(0, decoderRef)
while (decoderPool.size > DECODER_POOL_SIZE) {
decoderPool.removeAt(decoderPool.size - 1)
}
return decoderRef.decoder
}
private fun createTemporaryJpegExport(uri: Uri, mimeType: String, pageId: Int?): Uri { private fun createTemporaryJpegExport(uri: Uri, mimeType: String, pageId: Int?): Uri {
Log.d(LOG_TAG, "create JPEG export for uri=$uri mimeType=$mimeType pageId=$pageId") Log.d(LOG_TAG, "create JPEG export for uri=$uri mimeType=$mimeType pageId=$pageId")
val target = Glide.with(context) val target = Glide.with(context)
@ -180,7 +184,7 @@ class RegionFetcher internal constructor(
} }
} }
private data class LastDecoderRef( private data class DecoderRef(
val requestKey: Pair<Uri, Int?>, val requestKey: Pair<Uri, Int?>,
val decoder: BitmapRegionDecoder, val decoder: BitmapRegionDecoder,
) )
@ -188,5 +192,8 @@ class RegionFetcher internal constructor(
companion object { companion object {
private val LOG_TAG = LogUtils.createTag<RegionFetcher>() private val LOG_TAG = LogUtils.createTag<RegionFetcher>()
private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888 private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
private const val DECODER_POOL_SIZE = 3
private val decoderPool = ArrayList<DecoderRef>()
private val exportUris = HashMap<Pair<Uri, Int?>, Uri>()
} }
} }

View file

@ -22,8 +22,6 @@ import kotlin.math.ceil
class SvgRegionFetcher internal constructor( class SvgRegionFetcher internal constructor(
private val context: Context, private val context: Context,
) { ) {
private var lastSvgRef: LastSvgRef? = null
fun fetch( fun fetch(
uri: Uri, uri: Uri,
sizeBytes: Long?, sizeBytes: Long?,
@ -39,32 +37,12 @@ class SvgRegionFetcher internal constructor(
return return
} }
var currentSvgRef = lastSvgRef
if (currentSvgRef != null && currentSvgRef.uri != uri) {
currentSvgRef = null
}
try { try {
if (currentSvgRef == null) { val svg = getOrCreateDecoder(uri)
val newSvg = StorageUtils.openInputStream(context, uri)?.use { input -> if (svg == null) {
try { result.error("fetch-read-null", "failed to open file for uri=$uri regionRect=$regionRect", null)
SVG.getFromInputStream(SVGParserBufferedInputStream(input)) return
} catch (ex: SVGParseException) {
result.error("fetch-parse", "failed to parse SVG for uri=$uri regionRect=$regionRect", null)
return
}
}
if (newSvg == null) {
result.error("fetch-read-null", "failed to open file for uri=$uri regionRect=$regionRect", null)
return
}
newSvg.normalizeSize()
currentSvgRef = LastSvgRef(uri, newSvg)
} }
val svg = currentSvgRef.svg
lastSvgRef = currentSvgRef
// we scale the requested region accordingly to the viewbox size // we scale the requested region accordingly to the viewbox size
val viewBox = svg.documentViewBox val viewBox = svg.documentViewBox
@ -110,17 +88,42 @@ class SvgRegionFetcher internal constructor(
bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight) bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight)
val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true) val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true)
result.success(bytes) result.success(bytes)
} catch (e: SVGParseException) {
result.error("fetch-parse", "failed to parse SVG for uri=$uri regionRect=$regionRect", null)
} catch (e: Exception) { } catch (e: Exception) {
result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message) result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message)
} }
} }
private data class LastSvgRef( private fun getOrCreateDecoder(uri: Uri): SVG? {
var decoderRef = decoderPool.firstOrNull { it.uri == uri }
if (decoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
SVG.getFromInputStream(SVGParserBufferedInputStream(input))
}
if (newDecoder == null) {
return null
}
newDecoder.normalizeSize()
decoderRef = DecoderRef(uri, newDecoder)
} else {
decoderPool.remove(decoderRef)
}
decoderPool.add(0, decoderRef)
while (decoderPool.size > DECODER_POOL_SIZE) {
decoderPool.removeAt(decoderPool.size - 1)
}
return decoderRef.decoder
}
private data class DecoderRef(
val uri: Uri, val uri: Uri,
val svg: SVG, val decoder: SVG,
) )
companion object { companion object {
private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888 private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
private const val DECODER_POOL_SIZE = 3
private val decoderPool = ArrayList<DecoderRef>()
} }
} }

View file

@ -2,6 +2,7 @@ package deckers.thibault.aves.decoder
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.text.format.Formatter
import android.util.Log import android.util.Log
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder import com.bumptech.glide.GlideBuilder
@ -10,9 +11,17 @@ import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.ImageHeaderParser import com.bumptech.glide.load.ImageHeaderParser
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter
import com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool
import com.bumptech.glide.load.engine.cache.DiskCache
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.cache.LruResourceCache
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
import com.bumptech.glide.load.resource.bitmap.ExifInterfaceImageHeaderParser import com.bumptech.glide.load.resource.bitmap.ExifInterfaceImageHeaderParser
import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.StorageUtils import deckers.thibault.aves.utils.StorageUtils
@ -23,6 +32,30 @@ class AvesAppGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) { override fun applyOptions(context: Context, builder: GlideBuilder) {
// hide noisy warning (e.g. for images that can't be decoded) // hide noisy warning (e.g. for images that can't be decoded)
builder.setLogLevel(Log.ERROR) builder.setLogLevel(Log.ERROR)
// sizing
val memorySizeCalculator = MemorySizeCalculator.Builder(context).build()
builder.setMemorySizeCalculator(memorySizeCalculator)
val size: Int = memorySizeCalculator.bitmapPoolSize
if (size > 0) {
builder.setBitmapPool(LruBitmapPool(size.toLong()))
} else {
builder.setBitmapPool(BitmapPoolAdapter())
}
builder.setArrayPool(LruArrayPool(memorySizeCalculator.arrayPoolSizeInBytes))
builder.setMemoryCache(LruResourceCache(memorySizeCalculator.memoryCacheSize.toLong()))
val diskCacheSize = DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE
val internalCacheDiskCacheFactory = InternalCacheDiskCacheFactory(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize.toLong())
builder.setDiskCache(internalCacheDiskCacheFactory)
fun toMb(bytes: Int) = Formatter.formatFileSize(context, bytes.toLong())
Log.d(
LOG_TAG, "Glide disk cache size=${toMb(diskCacheSize)}" +
", memory cache size=${toMb(memorySizeCalculator.memoryCacheSize)}" +
", bitmap pool size=${toMb(memorySizeCalculator.bitmapPoolSize)}" +
", array pool size=${toMb(memorySizeCalculator.arrayPoolSizeInBytes)}"
)
} }
override fun registerComponents(context: Context, glide: Glide, registry: Registry) { override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
@ -34,6 +67,8 @@ class AvesAppGlideModule : AppGlideModule() {
override fun isManifestParsingEnabled(): Boolean = false override fun isManifestParsingEnabled(): Boolean = false
companion object { companion object {
private val LOG_TAG = LogUtils.createTag<AvesAppGlideModule>()
// request a fresh image with the highest quality format // request a fresh image with the highest quality format
val uncachedFullImageOptions = RequestOptions() val uncachedFullImageOptions = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888) .format(DecodeFormat.PREFER_ARGB_8888)

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Aves</string> <string name="app_name">ಎವೀಸ್</string>
<string name="app_widget_label">ಫೋಟೋ ಫ್ರೇಮ್</string> <string name="app_widget_label">ಫೋಟೋ ಫ್ರೇಮ್</string>
<string name="wallpaper">ವಾಲ್ಪೇಪರ್</string> <string name="wallpaper">ವಾಲ್ಪೇಪರ್</string>
<string name="videos_shortcut_short_label">ವೀಡಿಯೊಗಳು</string> <string name="videos_shortcut_short_label">ವೀಡಿಯೊಗಳು</string>
@ -8,4 +8,5 @@
<string name="analysis_notification_default_title">ಮೀಡಿಯಾ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತಿದೆ</string> <string name="analysis_notification_default_title">ಮೀಡಿಯಾ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತಿದೆ</string>
<string name="analysis_notification_action_stop">ನಿಲ್ಲಿಸಿ</string> <string name="analysis_notification_action_stop">ನಿಲ್ಲಿಸಿ</string>
<string name="search_shortcut_short_label">ಹುಡುಕಿ</string> <string name="search_shortcut_short_label">ಹುಡುಕಿ</string>
<string name="map_shortcut_short_label">ನಕ್ಷೆ</string>
</resources> </resources>

35
assets/terms.txt Normal file
View file

@ -0,0 +1,35 @@
Terms of Service
================
“Aves Gallery” is an open-source gallery and metadata explorer app allowing you to access and manage your local photos and videos.
The app is designed for legal, authorized and acceptable purposes.
Disclaimer
==========
The app is released “as-is”, without any warranty, responsibility or liability. Use of the app is at your own risk.
Privacy Policy
==============
The app does not collect any personal data. We never have access to your photos and videos. This also means that we cannot get them back for you if you delete them without backing them up.
Optionally, with your consent, the app accesses the inventory of installed apps to improve album display.
Optionally, with your consent, the app collects anonymous error and diagnostic data to improve the app quality. We use Firebase Crashlytics, and the anonymous data are stored on their servers. Please note that those are anonymous data, there is absolutely nothing personal about those data.
Contact
=======
Developer: Thibault Deckers
Email: gallery.aves@gmail.com
Website: https://github.com/deckerst/aves

View file

@ -2,4 +2,4 @@
<b>Navigation und Suche</b> ist ein wichtiger Bestandteil von <i>Aves</i>. Das Ziel besteht darin, dass Benutzer problemlos von Alben zu Fotos zu Tags zu Karten usw. wechseln können. <b>Navigation und Suche</b> ist ein wichtiger Bestandteil von <i>Aves</i>. Das Ziel besteht darin, dass Benutzer problemlos von Alben zu Fotos zu Tags zu Karten usw. wechseln können.
<i>Aves</i> lässt sich mit Android mit Funktionen wie <b>App-Verknüpfungen</b> und <b>globaler Suche</b> integrieren. Es funktioniert auch als <b>Medienbetrachter und -auswahl</b>. <i>Aves</i> integriert sich in Android (einschließlich Android TV) mit Funktionen wie <b>Widgets</b>, <b>App-Shortcuts</b>, <b>Bildschirmschoner</b> und der <b>globalen Suche</b> integrieren. Sie funktioniert auch als <b>Medienbetrachter und -Picker</b>.

View file

@ -0,0 +1,4 @@
In v1.12.9:
- play more kinds of motion photos
- enjoy the app in Galician and Kannada
Full changelog available on GitHub

View file

@ -0,0 +1,4 @@
In v1.12.9:
- play more kinds of motion photos
- enjoy the app in Galician and Kannada
Full changelog available on GitHub

View file

@ -1,5 +1,5 @@
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files. <i>ಏವೀಸ್</i> ನಿಮ್ಮ JPEG ಗಳು ಮತ್ತು MP4 ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ಎಲ್ಲಾ ರೀತಿಯ ಚಿತ್ರಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ನಿಭಾಯಿಸಬಲ್ಲದು, ಅಲ್ಲದೆ ವಿಶಿಷ್ಟವಾದ <b>ಬಹು-ಪುಟ TIFFಗಳು, SVGಗಳು, ಹಳೆಯ AVIಗಳು ಮತ್ತು ಹಲವು ಪ್ರಕಾರಗಳನ್ನು ಕೂಡ ಬೆಂಬಲಿಸುತ್ತದೆ</b> ಇದು <b>ಚಲನೆಯ ಫೋಟೋಗಳು</b>, <b>ಪನೋರಮಾಗಳು</b> (ಫೋಟೋ ಗೋಳಗಳು) <b>360° ವೀಡಿಯೊಗಳು</b>, ಹಾಗೆಯೇ <b>GeoTIFF</b> ಕಡತಗಳನ್ನು ಗುರುತಿಸಲು ನಿಮ್ಮ ಮಾಧ್ಯಮ ಸಂಗ್ರಹವನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡುತ್ತದೆ.
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc. <b>ನ್ಯಾವಿಗೇಷನ್ ಮತ್ತು ಹುಡುಕಾಟ</b> <i>ಏವೀಸ್</i>ನ ಒಂದು ಪ್ರಮುಖ ಭಾಗವಾಗಿದೆ. ಬಳಕೆದಾರರು ಆಲ್ಬಮ್‌ಗಳಿಂದ ಫೋಟೋಗಳಿಂದ ಟ್ಯಾಗ್‌ಗಳಿಗೆ ನಕ್ಷೆಗಳಿಗೆ ಸುಲಭವಾಗಿ ಹರಿಯುವುದು ಗುರಿಯಾಗಿದೆ.
<i>Aves</i> integrates with Android (including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>. <i>ಎವೀಸ್</i> ಆಂಡ್ರಾಯ್ಡ್ (ಟಿವಿ ಸೇರಿದಂತೆ) ನೊಂದಿಗೆ ಸಂಯೋಜಿಸುತ್ತದೆ, ಉದಾಹರಣೆಗೆ <b>ವಿಜೆಟ್‌ಗಳು</b>, <b>ಆ್ಯಪ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು</b>, <b>ಸ್ಕ್ರೀನ್ ಸೇವರ್</b> ಮತ್ತು <b>ಜಾಗತಿಕ ಹುಡುಕಾಟ</b> ನಿರ್ವಹಣೆ. ಇದು <b>ಮೀಡಿಯಾ ವೀಕ್ಷಕ ಮತ್ತು ಪಿಕ್ಕರ್</b> ಆಗಿಯೂ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

View file

@ -1 +1 @@
Gallery and metadata explorer ಗ್ಯಾಲರಿ ಮತ್ತು ಮೆಟಾಡೇಟಾ ಎಕ್ಸ್‌ಪ್ಲೋರರ್

View file

@ -10,7 +10,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@immutable @immutable
class UriImage extends ImageProvider<UriImage> with EquatableMixin { class FullImage extends ImageProvider<FullImage> with EquatableMixin {
final String uri, mimeType; final String uri, mimeType;
final int? pageId, rotationDegrees, sizeBytes; final int? pageId, rotationDegrees, sizeBytes;
final bool isFlipped, isAnimated; final bool isFlipped, isAnimated;
@ -19,7 +19,7 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
@override @override
List<Object?> get props => [uri, pageId, rotationDegrees, isFlipped, isAnimated, scale]; List<Object?> get props => [uri, pageId, rotationDegrees, isFlipped, isAnimated, scale];
const UriImage({ const FullImage({
required this.uri, required this.uri,
required this.mimeType, required this.mimeType,
required this.pageId, required this.pageId,
@ -31,12 +31,12 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
}); });
@override @override
Future<UriImage> obtainKey(ImageConfiguration configuration) { Future<FullImage> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<UriImage>(this); return SynchronousFuture<FullImage>(this);
} }
@override @override
ImageStreamCompleter loadImage(UriImage key, ImageDecoderCallback decode) { ImageStreamCompleter loadImage(FullImage key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>(); final chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
@ -59,11 +59,11 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
case MimeTypes.svg: case MimeTypes.svg:
return false; return false;
default: default:
return !isAnimated; return !isAnimated && !MimeTypes.isVideo(mimeType);
} }
} }
Future<ui.Codec> _loadAsync(UriImage key, ImageDecoderCallback decode, StreamController<ImageChunkEvent> chunkEvents) async { Future<ui.Codec> _loadAsync(FullImage key, ImageDecoderCallback decode, StreamController<ImageChunkEvent> chunkEvents) async {
assert(key == this); assert(key == this);
final request = ImageRequest( final request = ImageRequest(

View file

@ -1552,5 +1552,87 @@
"settingsViewerQuickActionEmpty": "Ingen knapper", "settingsViewerQuickActionEmpty": "Ingen knapper",
"@settingsViewerQuickActionEmpty": {}, "@settingsViewerQuickActionEmpty": {},
"chipActionFilterOut": "Filtrer ud", "chipActionFilterOut": "Filtrer ud",
"@chipActionFilterOut": {} "@chipActionFilterOut": {},
"mapStyleOsmHot": "Humanitært OSM",
"@mapStyleOsmHot": {},
"collectionDeselectSectionTooltip": "Fravælg sektion",
"@collectionDeselectSectionTooltip": {},
"editEntryLocationDialogImportGpx": "Importér GPX",
"@editEntryLocationDialogImportGpx": {},
"editEntryLocationDialogTimeShift": "Tidsskift",
"@editEntryLocationDialogTimeShift": {},
"videoStreamSelectionDialogTrack": "Spor",
"@videoStreamSelectionDialogTrack": {},
"albumGroupTier": "Efter kategori",
"@albumGroupTier": {},
"settingsVideoEnableHardwareAcceleration": "Hardwareacceleration",
"@settingsVideoEnableHardwareAcceleration": {},
"settingsViewerSectionTitle": "Fremviser",
"@settingsViewerSectionTitle": {},
"openMapPageTooltip": "Se på kortside",
"@openMapPageTooltip": {},
"settingsCollectionBurstPatternsTile": "Filnavnmønstre",
"@settingsCollectionBurstPatternsTile": {},
"wallpaperUseScrollEffect": "Brug rulleeffekt på startside",
"@wallpaperUseScrollEffect": {},
"editEntryDateDialogSourceFileModifiedDate": "Filens ændringsdato",
"@editEntryDateDialogSourceFileModifiedDate": {},
"editEntryDateDialogShift": "Skift",
"@editEntryDateDialogShift": {},
"chipActionDecompose": "Split",
"@chipActionDecompose": {},
"coordinateFormatDdm": "DDM",
"@coordinateFormatDdm": {},
"videoActionShowNextFrame": "Vis næste frame",
"@videoActionShowNextFrame": {},
"mapStyleStamenWatercolor": "Stamen Watercolor",
"@mapStyleStamenWatercolor": {},
"drawerCollectionAll": "Alle samlinger",
"@drawerCollectionAll": {},
"settingsThumbnailShowVideoDuration": "Vis videovarighed",
"@settingsThumbnailShowVideoDuration": {},
"settingsThemeColorHighlights": "Farvemarkeringer",
"@settingsThemeColorHighlights": {},
"viewerInfoSearchEmpty": "Ingen matchende nøgler",
"@viewerInfoSearchEmpty": {},
"removeEntryMetadataDialogAll": "Alle",
"@removeEntryMetadataDialogAll": {},
"aboutCreditsSectionTitle": "Kreditering",
"@aboutCreditsSectionTitle": {},
"settingsCollectionQuickActionTabSelecting": "Valg",
"@settingsCollectionQuickActionTabSelecting": {},
"settingsCollectionQuickActionTabBrowsing": "Browsing",
"@settingsCollectionQuickActionTabBrowsing": {},
"settingsViewerQuickActionEditorBanner": "Tryk og hold for at flytte knapper og vælge, hvilke handlinger der vises i fremviseren.",
"@settingsViewerQuickActionEditorBanner": {},
"settingsAllowInstalledAppAccess": "Tillad adgang til app-lager",
"@settingsAllowInstalledAppAccess": {},
"viewerInfoBackToViewerTooltip": "Tilbage til fremviser",
"@viewerInfoBackToViewerTooltip": {},
"videoActionShowPreviousFrame": "Vis forrige frame",
"@videoActionShowPreviousFrame": {},
"collectionSelectSectionTooltip": "Vælg sektion",
"@collectionSelectSectionTooltip": {},
"videoStreamSelectionDialogNoSelection": "Der er ingen andre spor.",
"@videoStreamSelectionDialogNoSelection": {},
"addShortcutDialogLabel": "Genvejsetiket",
"@addShortcutDialogLabel": {},
"moveUndatedConfirmationDialogMessage": "Gem elementdatoer, før du fortsætter?",
"@moveUndatedConfirmationDialogMessage": {},
"albumMimeTypeMixed": "Blandet",
"@albumMimeTypeMixed": {},
"videoActionCaptureFrame": "Tag billede af frame",
"@videoActionCaptureFrame": {},
"videoActionSelectStreams": "Vælg spor",
"@videoActionSelectStreams": {},
"videoActionABRepeat": "A-B gentagelse",
"@videoActionABRepeat": {},
"viewerActionLock": "Lås fremviser",
"@viewerActionLock": {},
"viewerActionUnlock": "Oplås fremviser",
"@viewerActionUnlock": {},
"keepScreenOnViewerOnly": "Kun fremvisningsside",
"@keepScreenOnViewerOnly": {},
"widgetOpenPageViewer": "Åbn fremviser",
"@widgetOpenPageViewer": {}
} }

View file

@ -1410,5 +1410,9 @@
"chipActionDecompose": "Aufschlüsseln", "chipActionDecompose": "Aufschlüsseln",
"@chipActionDecompose": {}, "@chipActionDecompose": {},
"editEntryLocationDialogImportGpx": "GPX importieren", "editEntryLocationDialogImportGpx": "GPX importieren",
"@editEntryLocationDialogImportGpx": {} "@editEntryLocationDialogImportGpx": {},
"editEntryLocationDialogTimeShift": "Zeitverschiebung",
"@editEntryLocationDialogTimeShift": {},
"removeEntryMetadataDialogAll": "Alle",
"@removeEntryMetadataDialogAll": {}
} }

File diff suppressed because it is too large Load diff

View file

@ -1605,7 +1605,7 @@
"@mapStyleOsmLiberty": {}, "@mapStyleOsmLiberty": {},
"mapStyleOpenTopoMap": "OpenTopoMap", "mapStyleOpenTopoMap": "OpenTopoMap",
"@mapStyleOpenTopoMap": {}, "@mapStyleOpenTopoMap": {},
"mapAttributionOpenTopoMap": "[SRTM](https://www.earthdata.nasa.gov/sensors/srtm) | 地圖由 [OpenTopoMap](https://opentopomap.org/),以 [CC BY-SA](https://creativecommons.org/licenses/by-sa/3.0/) 授權", "mapAttributionOpenTopoMap": "[SRTM](https://www.earthdata.nasa.gov/sensors/srtm) | 地圖由 [OpenTopoMap](https://opentopomap.org/), [CC BY-SA](https://creativecommons.org/licenses/by-sa/3.0/)",
"@mapAttributionOpenTopoMap": {}, "@mapAttributionOpenTopoMap": {},
"sortByDuration": "按時長", "sortByDuration": "按時長",
"@sortByDuration": {} "@sortByDuration": {}

View file

@ -315,7 +315,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get entryActionRemoveFavourite => 'Fjern fra favoritter'; String get entryActionRemoveFavourite => 'Fjern fra favoritter';
@override @override
String get videoActionCaptureFrame => 'Capture frame'; String get videoActionCaptureFrame => 'Tag billede af frame';
@override @override
String get videoActionMute => 'Slå lyden fra'; String get videoActionMute => 'Slå lyden fra';
@ -336,19 +336,19 @@ class AppLocalizationsDa extends AppLocalizations {
String get videoActionSkip10 => 'Spol 10 sekunder frem'; String get videoActionSkip10 => 'Spol 10 sekunder frem';
@override @override
String get videoActionShowPreviousFrame => 'Show previous frame'; String get videoActionShowPreviousFrame => 'Vis forrige frame';
@override @override
String get videoActionShowNextFrame => 'Show next frame'; String get videoActionShowNextFrame => 'Vis næste frame';
@override @override
String get videoActionSelectStreams => 'Select tracks'; String get videoActionSelectStreams => 'Vælg spor';
@override @override
String get videoActionSetSpeed => 'Afspilningshastighed'; String get videoActionSetSpeed => 'Afspilningshastighed';
@override @override
String get videoActionABRepeat => 'A-B repeat'; String get videoActionABRepeat => 'A-B gentagelse';
@override @override
String get videoRepeatActionSetStart => 'Sæt start'; String get videoRepeatActionSetStart => 'Sæt start';
@ -360,10 +360,10 @@ class AppLocalizationsDa extends AppLocalizations {
String get viewerActionSettings => 'Indstillinger'; String get viewerActionSettings => 'Indstillinger';
@override @override
String get viewerActionLock => 'Lock viewer'; String get viewerActionLock => 'Lås fremviser';
@override @override
String get viewerActionUnlock => 'Unlock viewer'; String get viewerActionUnlock => 'Oplås fremviser';
@override @override
String get slideshowActionResume => 'Genoptag'; String get slideshowActionResume => 'Genoptag';
@ -548,7 +548,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get keepScreenOnVideoPlayback => 'Under videoafspilning'; String get keepScreenOnVideoPlayback => 'Under videoafspilning';
@override @override
String get keepScreenOnViewerOnly => 'Viewer page only'; String get keepScreenOnViewerOnly => 'Kun fremvisningsside';
@override @override
String get keepScreenOnAlways => 'Altid'; String get keepScreenOnAlways => 'Altid';
@ -575,7 +575,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get mapStyleOpenTopoMap => 'OpenTopoMap'; String get mapStyleOpenTopoMap => 'OpenTopoMap';
@override @override
String get mapStyleOsmHot => 'Humanitarian OSM'; String get mapStyleOsmHot => 'Humanitært OSM';
@override @override
String get mapStyleStamenWatercolor => 'Stamen Watercolor'; String get mapStyleStamenWatercolor => 'Stamen Watercolor';
@ -701,7 +701,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get widgetOpenPageCollection => 'Åbn samling'; String get widgetOpenPageCollection => 'Åbn samling';
@override @override
String get widgetOpenPageViewer => 'Open viewer'; String get widgetOpenPageViewer => 'Åbn fremviser';
@override @override
String get widgetTapUpdateWidget => 'Opdater widget'; String get widgetTapUpdateWidget => 'Opdater widget';
@ -756,7 +756,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get nameConflictDialogMultipleSourceMessage => 'Nogle filer har samme navn.'; String get nameConflictDialogMultipleSourceMessage => 'Nogle filer har samme navn.';
@override @override
String get addShortcutDialogLabel => 'Shortcut label'; String get addShortcutDialogLabel => 'Genvejsetiket';
@override @override
String get addShortcutButtonLabel => 'TILFØJ'; String get addShortcutButtonLabel => 'TILFØJ';
@ -793,7 +793,7 @@ class AppLocalizationsDa extends AppLocalizations {
} }
@override @override
String get moveUndatedConfirmationDialogMessage => 'Save item dates before proceeding?'; String get moveUndatedConfirmationDialogMessage => 'Gem elementdatoer, før du fortsætter?';
@override @override
String get moveUndatedConfirmationDialogSetDate => 'Gem datoer'; String get moveUndatedConfirmationDialogSetDate => 'Gem datoer';
@ -976,10 +976,10 @@ class AppLocalizationsDa extends AppLocalizations {
String get editEntryDateDialogExtractFromTitle => 'Udtræk fra titel'; String get editEntryDateDialogExtractFromTitle => 'Udtræk fra titel';
@override @override
String get editEntryDateDialogShift => 'Shift'; String get editEntryDateDialogShift => 'Skift';
@override @override
String get editEntryDateDialogSourceFileModifiedDate => 'File modified date'; String get editEntryDateDialogSourceFileModifiedDate => 'Filens ændringsdato';
@override @override
String get durationDialogHours => 'Timer'; String get durationDialogHours => 'Timer';
@ -1000,7 +1000,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get editEntryLocationDialogChooseOnMap => 'Vælg på kort'; String get editEntryLocationDialogChooseOnMap => 'Vælg på kort';
@override @override
String get editEntryLocationDialogImportGpx => 'Import GPX'; String get editEntryLocationDialogImportGpx => 'Importér GPX';
@override @override
String get editEntryLocationDialogLatitude => 'Breddegrad'; String get editEntryLocationDialogLatitude => 'Breddegrad';
@ -1009,7 +1009,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get editEntryLocationDialogLongitude => 'Længdegrad'; String get editEntryLocationDialogLongitude => 'Længdegrad';
@override @override
String get editEntryLocationDialogTimeShift => 'Time shift'; String get editEntryLocationDialogTimeShift => 'Tidsskift';
@override @override
String get locationPickerUseThisLocationButton => 'Brug denne placering'; String get locationPickerUseThisLocationButton => 'Brug denne placering';
@ -1021,7 +1021,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get removeEntryMetadataDialogTitle => 'Fjernelse af metadata'; String get removeEntryMetadataDialogTitle => 'Fjernelse af metadata';
@override @override
String get removeEntryMetadataDialogAll => 'All'; String get removeEntryMetadataDialogAll => 'Alle';
@override @override
String get removeEntryMetadataDialogMore => 'Mere'; String get removeEntryMetadataDialogMore => 'Mere';
@ -1045,10 +1045,10 @@ class AppLocalizationsDa extends AppLocalizations {
String get videoStreamSelectionDialogOff => 'Fra'; String get videoStreamSelectionDialogOff => 'Fra';
@override @override
String get videoStreamSelectionDialogTrack => 'Track'; String get videoStreamSelectionDialogTrack => 'Spor';
@override @override
String get videoStreamSelectionDialogNoSelection => 'There are no other tracks.'; String get videoStreamSelectionDialogNoSelection => 'Der er ingen andre spor.';
@override @override
String get genericSuccessFeedback => 'Færdig!'; String get genericSuccessFeedback => 'Færdig!';
@ -1174,7 +1174,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get aboutDataUsageClearCache => 'Ryd cache'; String get aboutDataUsageClearCache => 'Ryd cache';
@override @override
String get aboutCreditsSectionTitle => 'Credits'; String get aboutCreditsSectionTitle => 'Kreditering';
@override @override
String get aboutCreditsWorldAtlas1 => 'Denne app bruger en TopoJSON-fil fra'; String get aboutCreditsWorldAtlas1 => 'Denne app bruger en TopoJSON-fil fra';
@ -1428,10 +1428,10 @@ class AppLocalizationsDa extends AppLocalizations {
String get collectionEmptyGrantAccessButtonLabel => 'Giv adgang'; String get collectionEmptyGrantAccessButtonLabel => 'Giv adgang';
@override @override
String get collectionSelectSectionTooltip => 'Select section'; String get collectionSelectSectionTooltip => 'Vælg sektion';
@override @override
String get collectionDeselectSectionTooltip => 'Deselect section'; String get collectionDeselectSectionTooltip => 'Fravælg sektion';
@override @override
String get drawerAboutButton => 'Om'; String get drawerAboutButton => 'Om';
@ -1440,7 +1440,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get drawerSettingsButton => 'Indstillinger'; String get drawerSettingsButton => 'Indstillinger';
@override @override
String get drawerCollectionAll => 'All collection'; String get drawerCollectionAll => 'Alle samlinger';
@override @override
String get drawerCollectionFavourites => 'Favoritter'; String get drawerCollectionFavourites => 'Favoritter';
@ -1530,7 +1530,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get sortOrderLongestFirst => 'Længste først'; String get sortOrderLongestFirst => 'Længste først';
@override @override
String get albumGroupTier => 'By tier'; String get albumGroupTier => 'Efter kategori';
@override @override
String get albumGroupType => 'Efter type'; String get albumGroupType => 'Efter type';
@ -1542,7 +1542,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get albumGroupNone => 'Gruppér ikke'; String get albumGroupNone => 'Gruppér ikke';
@override @override
String get albumMimeTypeMixed => 'Mixed'; String get albumMimeTypeMixed => 'Blandet';
@override @override
String get albumPickPageTitleCopy => 'Kopiér til album'; String get albumPickPageTitleCopy => 'Kopiér til album';
@ -1794,7 +1794,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get settingsThumbnailShowRawIcon => 'Vis RAW-ikon'; String get settingsThumbnailShowRawIcon => 'Vis RAW-ikon';
@override @override
String get settingsThumbnailShowVideoDuration => 'Show video duration'; String get settingsThumbnailShowVideoDuration => 'Vis videovarighed';
@override @override
String get settingsCollectionQuickActionsTile => 'Hurtighandlinger'; String get settingsCollectionQuickActionsTile => 'Hurtighandlinger';
@ -1806,7 +1806,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get settingsCollectionQuickActionTabBrowsing => 'Browsing'; String get settingsCollectionQuickActionTabBrowsing => 'Browsing';
@override @override
String get settingsCollectionQuickActionTabSelecting => 'Selecting'; String get settingsCollectionQuickActionTabSelecting => 'Valg';
@override @override
String get settingsCollectionBrowsingQuickActionEditorBanner => 'Tryk og hold for at flytte knapper og vælge, hvilke handlinger der vises, når du gennemser elementer.'; String get settingsCollectionBrowsingQuickActionEditorBanner => 'Tryk og hold for at flytte knapper og vælge, hvilke handlinger der vises, når du gennemser elementer.';
@ -1815,13 +1815,13 @@ class AppLocalizationsDa extends AppLocalizations {
String get settingsCollectionSelectionQuickActionEditorBanner => 'Tryk og hold for at flytte knapper og vælge, hvilke handlinger der vises, når du vælger elementer.'; String get settingsCollectionSelectionQuickActionEditorBanner => 'Tryk og hold for at flytte knapper og vælge, hvilke handlinger der vises, når du vælger elementer.';
@override @override
String get settingsCollectionBurstPatternsTile => 'Burst patterns'; String get settingsCollectionBurstPatternsTile => 'Filnavnmønstre';
@override @override
String get settingsCollectionBurstPatternsNone => 'Ingen'; String get settingsCollectionBurstPatternsNone => 'Ingen';
@override @override
String get settingsViewerSectionTitle => 'Viewer'; String get settingsViewerSectionTitle => 'Fremviser';
@override @override
String get settingsViewerGestureSideTapNext => 'Tryk på skærmkanterne for at vise forrige/næste element'; String get settingsViewerGestureSideTapNext => 'Tryk på skærmkanterne for at vise forrige/næste element';
@ -1845,7 +1845,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get settingsViewerQuickActionEditorPageTitle => 'Hurtighandlinger'; String get settingsViewerQuickActionEditorPageTitle => 'Hurtighandlinger';
@override @override
String get settingsViewerQuickActionEditorBanner => 'Touch and hold to move buttons and select which actions are displayed in the viewer.'; String get settingsViewerQuickActionEditorBanner => 'Tryk og hold for at flytte knapper og vælge, hvilke handlinger der vises i fremviseren.';
@override @override
String get settingsViewerQuickActionEditorDisplayedButtonsSectionTitle => 'Viste knapper'; String get settingsViewerQuickActionEditorDisplayedButtonsSectionTitle => 'Viste knapper';
@ -1938,7 +1938,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get settingsVideoPlaybackPageTitle => 'Afspilning'; String get settingsVideoPlaybackPageTitle => 'Afspilning';
@override @override
String get settingsVideoEnableHardwareAcceleration => 'Hardware acceleration'; String get settingsVideoEnableHardwareAcceleration => 'Hardwareacceleration';
@override @override
String get settingsVideoAutoPlay => 'Afspil automatisk'; String get settingsVideoAutoPlay => 'Afspil automatisk';
@ -2031,7 +2031,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get settingsPrivacySectionTitle => 'Privatliv'; String get settingsPrivacySectionTitle => 'Privatliv';
@override @override
String get settingsAllowInstalledAppAccess => 'Allow access to app inventory'; String get settingsAllowInstalledAppAccess => 'Tillad adgang til app-lager';
@override @override
String get settingsAllowInstalledAppAccessSubtitle => 'Bruges til at forbedre albumvisning'; String get settingsAllowInstalledAppAccessSubtitle => 'Bruges til at forbedre albumvisning';
@ -2106,7 +2106,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get settingsThemeBrightnessDialogTitle => 'Tema'; String get settingsThemeBrightnessDialogTitle => 'Tema';
@override @override
String get settingsThemeColorHighlights => 'Color highlights'; String get settingsThemeColorHighlights => 'Farvemarkeringer';
@override @override
String get settingsThemeEnableDynamicColor => 'Dynamisk farve'; String get settingsThemeEnableDynamicColor => 'Dynamisk farve';
@ -2210,7 +2210,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get viewerInfoPageTitle => 'Info'; String get viewerInfoPageTitle => 'Info';
@override @override
String get viewerInfoBackToViewerTooltip => 'Back to viewer'; String get viewerInfoBackToViewerTooltip => 'Tilbage til fremviser';
@override @override
String get viewerInfoUnknown => 'ukendt'; String get viewerInfoUnknown => 'ukendt';
@ -2279,7 +2279,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get mapAttributionStamen => 'Fliser af [Stamen Design](https://stamen.com), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0)'; String get mapAttributionStamen => 'Fliser af [Stamen Design](https://stamen.com), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0)';
@override @override
String get openMapPageTooltip => 'View on Map page'; String get openMapPageTooltip => 'Se på kortside';
@override @override
String get mapEmptyRegion => 'Ingen billeder i denne region'; String get mapEmptyRegion => 'Ingen billeder i denne region';
@ -2297,7 +2297,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get viewerInfoSearchFieldLabel => 'Søg i metadata'; String get viewerInfoSearchFieldLabel => 'Søg i metadata';
@override @override
String get viewerInfoSearchEmpty => 'No matching keys'; String get viewerInfoSearchEmpty => 'Ingen matchende nøgler';
@override @override
String get viewerInfoSearchSuggestionDate => 'Dato og tid'; String get viewerInfoSearchSuggestionDate => 'Dato og tid';
@ -2315,7 +2315,7 @@ class AppLocalizationsDa extends AppLocalizations {
String get viewerInfoSearchSuggestionRights => 'Rettigheder'; String get viewerInfoSearchSuggestionRights => 'Rettigheder';
@override @override
String get wallpaperUseScrollEffect => 'Use scroll effect on home screen'; String get wallpaperUseScrollEffect => 'Brug rulleeffekt på startside';
@override @override
String get tagEditorPageTitle => 'Rediger Tags'; String get tagEditorPageTitle => 'Rediger Tags';

View file

@ -1006,7 +1006,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get editEntryLocationDialogLongitude => 'Längengrad'; String get editEntryLocationDialogLongitude => 'Längengrad';
@override @override
String get editEntryLocationDialogTimeShift => 'Time shift'; String get editEntryLocationDialogTimeShift => 'Zeitverschiebung';
@override @override
String get locationPickerUseThisLocationButton => 'Diesen Standort verwenden'; String get locationPickerUseThisLocationButton => 'Diesen Standort verwenden';
@ -1018,7 +1018,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get removeEntryMetadataDialogTitle => 'Entfernung von Metadaten'; String get removeEntryMetadataDialogTitle => 'Entfernung von Metadaten';
@override @override
String get removeEntryMetadataDialogAll => 'All'; String get removeEntryMetadataDialogAll => 'Alle';
@override @override
String get removeEntryMetadataDialogMore => 'Mehr'; String get removeEntryMetadataDialogMore => 'Mehr';

File diff suppressed because it is too large Load diff

View file

@ -4548,7 +4548,7 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
String get mapAttributionOsmLiberty => '地圖由 [OpenMapTiles](https://www.openmaptiles.org/) 所提供,以 [CC BY](http://creativecommons.org/licenses/by/4.0) 授權 • 托管於 [OSM Americana](https://tile.ourmap.us)'; String get mapAttributionOsmLiberty => '地圖由 [OpenMapTiles](https://www.openmaptiles.org/) 所提供,以 [CC BY](http://creativecommons.org/licenses/by/4.0) 授權 • 托管於 [OSM Americana](https://tile.ourmap.us)';
@override @override
String get mapAttributionOpenTopoMap => '[SRTM](https://www.earthdata.nasa.gov/sensors/srtm) | 地圖由 [OpenTopoMap](https://opentopomap.org/),以 [CC BY-SA](https://creativecommons.org/licenses/by-sa/3.0/) 授權'; String get mapAttributionOpenTopoMap => '[SRTM](https://www.earthdata.nasa.gov/sensors/srtm) | 地圖由 [OpenTopoMap](https://opentopomap.org/), [CC BY-SA](https://creativecommons.org/licenses/by-sa/3.0/)';
@override @override
String get mapAttributionOsmHot => '繪製於 [HOT](https://www.hotosm.org/) • 主辦方 [OSM France](https://openstreetmap.fr/)'; String get mapAttributionOsmHot => '繪製於 [HOT](https://www.hotosm.org/) • 主辦方 [OSM France](https://openstreetmap.fr/)';

View file

@ -130,6 +130,8 @@ class Contributors {
Contributor('pitroig', 'ona@riseup.net'), Contributor('pitroig', 'ona@riseup.net'),
Contributor('Rubén Castiñeiras Lorenzo', 'rcasl@outlook.com'), Contributor('Rubén Castiñeiras Lorenzo', 'rcasl@outlook.com'),
Contributor('hanyang cheng', 'cinxiafortis@tutanota.de'), Contributor('hanyang cheng', 'cinxiafortis@tutanota.de'),
Contributor('Chethan', 'chethan@users.noreply.hosted.weblate.org'),
Contributor('Prasannakumar T Bhat', 'pbhat99@gmail.com'),
// Contributor('Femini', 'nizamismidov4@gmail.com'), // Azerbaijani // Contributor('Femini', 'nizamismidov4@gmail.com'), // Azerbaijani
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali // Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese // Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
@ -142,7 +144,6 @@ class Contributors {
// Contributor('AJ07', 'ajaykumarmeena676@gmail.com'), // Hindi // Contributor('AJ07', 'ajaykumarmeena676@gmail.com'), // Hindi
// Contributor('Sartaj', 'ssaarrttaajj111@gmail.com'), // Hindi // Contributor('Sartaj', 'ssaarrttaajj111@gmail.com'), // Hindi
// Contributor('Anurag Samota', 'anuragsamotasamota@gmail.com'), // Hindi // Contributor('Anurag Samota', 'anuragsamotasamota@gmail.com'), // Hindi
// Contributor('Chethan', 'chethan@users.noreply.hosted.weblate.org'), // Kannada
// Contributor('GoRaN', 'gorangharib.909@gmail.com'), // Kurdish (Central) // Contributor('GoRaN', 'gorangharib.909@gmail.com'), // Kurdish (Central)
// Contributor('Rasti K5', 'rasti.khdhr@gmail.com'), // Kurdish (Central) // Contributor('Rasti K5', 'rasti.khdhr@gmail.com'), // Kurdish (Central)
// Contributor('Raman', 'xysed@tutanota.com'), // Malayalam // Contributor('Raman', 'xysed@tutanota.com'), // Malayalam

View file

@ -496,13 +496,8 @@ class LocalMediaDbUpgrader {
static Future<void> _upgradeFrom14(Database db) async { static Future<void> _upgradeFrom14(Database db) async {
debugPrint('upgrading DB from v14'); debugPrint('upgrading DB from v14');
// no schema changes, but v1.12.4 may have corrupted the DB, // transitional upgrade previously used to sanitize rebuildable tables
// so we clear rebuildable tables // (dateTakenTable, metadataTable, addressTable, trashTable, videoPlaybackTable)
final tables = [dateTakenTable, metadataTable, addressTable, trashTable, videoPlaybackTable]; // for users with a potentially corrupted DB following upgrade to v1.12.4
await Future.forEach(tables, (table) async {
if (await db.tableExists(table)) {
await db.delete(table, where: '1');
}
});
} }
} }

View file

@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/image_providers/full_image_provider.dart';
import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/thumbnail_provider.dart';
import 'package:aves/image_providers/uri_image_provider.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class EntryCache { class EntryCache {
@ -30,7 +30,7 @@ class EntryCache {
int? pageId; int? pageId;
// evict fullscreen image // evict fullscreen image
await UriImage( await FullImage(
uri: uri, uri: uri,
mimeType: mimeType, mimeType: mimeType,
pageId: pageId, pageId: pageId,

View file

@ -1,8 +1,8 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/image_providers/full_image_provider.dart';
import 'package:aves/image_providers/region_provider.dart'; import 'package:aves/image_providers/region_provider.dart';
import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/thumbnail_provider.dart';
import 'package:aves/image_providers/uri_image_provider.dart';
import 'package:aves/model/entry/cache.dart'; import 'package:aves/model/entry/cache.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/math_utils.dart';
@ -49,7 +49,7 @@ extension ExtraAvesEntryImages on AvesEntry {
)); ));
} }
UriImage get uriImage => UriImage( FullImage get fullImage => FullImage(
uri: uri, uri: uri,
mimeType: mimeType, mimeType: mimeType,
pageId: pageId, pageId: pageId,

View file

@ -157,7 +157,7 @@ class MappedGeoTiff with MapOverlay {
String get id => entry.uri; String get id => entry.uri;
@override @override
ImageProvider get imageProvider => entry.uriImage; ImageProvider get imageProvider => entry.fullImage;
@override @override
bool get canOverlay => center != null; bool get canOverlay => center != null;

View file

@ -9,6 +9,7 @@ import 'package:aves/services/common/output_buffer.dart';
import 'package:aves/services/common/service_policy.dart'; import 'package:aves/services/common/service_policy.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/services/media/byte_receiving_codec.dart'; import 'package:aves/services/media/byte_receiving_codec.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:streams_channel/streams_channel.dart'; import 'package:streams_channel/streams_channel.dart';
@ -55,7 +56,9 @@ abstract class MediaFetchService {
int? priority, int? priority,
}); });
Future<void> clearSizedThumbnailDiskCache(); Future<void> clearImageDiskCache();
Future<void> clearImageMemoryCache();
bool cancelRegion(Object taskKey); bool cancelRegion(Object taskKey);
@ -255,9 +258,18 @@ class PlatformMediaFetchService implements MediaFetchService {
} }
@override @override
Future<void> clearSizedThumbnailDiskCache() async { Future<void> clearImageDiskCache() async {
try { try {
return _platformObject.invokeMethod('clearSizedThumbnailDiskCache'); return _platformObject.invokeMethod('clearImageDiskCache');
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
}
@override
Future<void> clearImageMemoryCache() async {
try {
return _platformObject.invokeMethod('clearImageMemoryCache');
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
@ -317,7 +329,7 @@ class PlatformMediaFetchService implements MediaFetchService {
} }
@immutable @immutable
class ImageRequest { class ImageRequest extends Equatable {
final String uri; final String uri;
final String mimeType; final String mimeType;
final int? rotationDegrees; final int? rotationDegrees;
@ -327,14 +339,17 @@ class ImageRequest {
final int? sizeBytes; final int? sizeBytes;
final BytesReceivedCallback? onBytesReceived; final BytesReceivedCallback? onBytesReceived;
@override
List<Object?> get props => [uri, mimeType, rotationDegrees, isFlipped, isAnimated, pageId, sizeBytes, onBytesReceived];
const ImageRequest( const ImageRequest(
this.uri, this.uri,
this.mimeType, { this.mimeType, {
required this.rotationDegrees, required this.rotationDegrees,
required this.isFlipped, required this.isFlipped,
required this.isAnimated, required this.isAnimated,
required this.pageId, required this.pageId,
required this.sizeBytes, required this.sizeBytes,
this.onBytesReceived, this.onBytesReceived,
}); });
} }

View file

@ -88,7 +88,7 @@ class _AboutDataUsageState extends State<AboutDataUsage> with FeedbackMixin {
onPressed: () async { onPressed: () async {
await storageService.deleteTempDirectory(); await storageService.deleteTempDirectory();
await storageService.deleteExternalCache(); await storageService.deleteExternalCache();
await mediaFetchService.clearSizedThumbnailDiskCache(); await mediaFetchService.clearImageDiskCache();
imageCache.clear(); imageCache.clear();
_reload(); _reload();
setState(() {}); setState(() {});

View file

@ -70,7 +70,6 @@ class AvesApp extends StatefulWidget {
'fi', // Finnish 'fi', // Finnish
'he', // Hebrew 'he', // Hebrew
'hi', // Hindi 'hi', // Hindi
'kn', // Kannada
'ml', // Malayalam 'ml', // Malayalam
'my', // Burmese 'my', // Burmese
'or', // Odia 'or', // Odia

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
@ -220,6 +221,7 @@ class AvesFloatingBar extends StatefulWidget {
class _AvesFloatingBarState extends State<AvesFloatingBar> with RouteAware { class _AvesFloatingBarState extends State<AvesFloatingBar> with RouteAware {
// prevent expensive blurring when the current page is hidden // prevent expensive blurring when the current page is hidden
final ValueNotifier<bool> _isBlurAllowedNotifier = ValueNotifier(true); final ValueNotifier<bool> _isBlurAllowedNotifier = ValueNotifier(true);
Timer? _blurBlockTimer;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
@ -240,6 +242,8 @@ class _AvesFloatingBarState extends State<AvesFloatingBar> with RouteAware {
@override @override
void didPopNext() { void didPopNext() {
// post to prevent single frame flash during hero // post to prevent single frame flash during hero
_blurBlockTimer?.cancel();
_blurBlockTimer = null;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) { if (mounted) {
_isBlurAllowedNotifier.value = true; _isBlurAllowedNotifier.value = true;
@ -249,8 +253,9 @@ class _AvesFloatingBarState extends State<AvesFloatingBar> with RouteAware {
@override @override
void didPushNext() { void didPushNext() {
// post to prevent single frame flash during hero // delay blur disabling, otherwise visual artifacts appear during page transition with Impeller
WidgetsBinding.instance.addPostFrameCallback((_) { _blurBlockTimer?.cancel();
_blurBlockTimer = Timer(ADurations.pageTransitionLoose, () {
if (mounted) { if (mounted) {
_isBlurAllowedNotifier.value = false; _isBlurAllowedNotifier.value = false;
} }

View file

@ -12,6 +12,20 @@ class DebugCacheSection extends StatefulWidget {
} }
class _DebugCacheSectionState extends State<DebugCacheSection> with AutomaticKeepAliveClientMixin { class _DebugCacheSectionState extends State<DebugCacheSection> with AutomaticKeepAliveClientMixin {
final TextEditingController _imageCacheSizeTextController = TextEditingController();
@override
void initState() {
super.initState();
_imageCacheSizeTextController.text = '${imageCache.maximumSizeBytes}';
}
@override
void dispose() {
_imageCacheSizeTextController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
@ -41,6 +55,31 @@ class _DebugCacheSectionState extends State<DebugCacheSection> with AutomaticKee
), ),
], ],
), ),
Row(
children: [
Expanded(
child: TextField(
controller: _imageCacheSizeTextController,
decoration: const InputDecoration(labelText: 'imageCache size bytes'),
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
final size = int.tryParse(_imageCacheSizeTextController.text);
if (size != null) {
imageCache.maximumSizeBytes = size;
} else {
_imageCacheSizeTextController.text = '${imageCache.maximumSizeBytes}';
}
setState(() {});
},
child: const Text('Apply'),
),
],
),
const Divider(),
Row( Row(
children: [ children: [
const Expanded( const Expanded(
@ -48,7 +87,19 @@ class _DebugCacheSectionState extends State<DebugCacheSection> with AutomaticKee
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
onPressed: mediaFetchService.clearSizedThumbnailDiskCache, onPressed: mediaFetchService.clearImageDiskCache,
child: const Text('Clear'),
),
],
),
Row(
children: [
const Expanded(
child: Text('Glide memory cache: ?'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: mediaFetchService.clearImageMemoryCache,
child: const Text('Clear'), child: const Text('Clear'),
), ),
], ],

View file

@ -24,6 +24,7 @@ class SupportedLocales {
'is': 'Íslenska', 'is': 'Íslenska',
'it': 'Italiano', 'it': 'Italiano',
'ja': '日本語', 'ja': '日本語',
'kn': 'ಕನ್ನಡ',
'ko': '한국어', 'ko': '한국어',
'lt': 'Lietuvių', 'lt': 'Lietuvių',
'nb': 'Norsk (Bokmål)', 'nb': 'Norsk (Bokmål)',

View file

@ -118,7 +118,7 @@ class EntryPrinter with FeedbackMixin {
} }
} else { } else {
return pdf.Image( return pdf.Image(
await flutterImageProvider(entry.uriImage), await flutterImageProvider(entry.fullImage),
fit: _fit, fit: _fit,
); );
} }

View file

@ -172,7 +172,7 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin {
} else { } else {
// provider image is already rotated, but not cropped // provider image is already rotated, but not cropped
needCrop = true; needCrop = true;
provider = entry.uriImage; provider = entry.fullImage;
} }
} }
if (provider == null) return null; if (provider == null) return null;

View file

@ -90,7 +90,7 @@ class _PanoramaPageState extends State<PanoramaPage> {
} }
}, },
child: Image( child: Image(
image: entry.uriImage, image: entry.fullImage,
), ),
), ),
Positioned( Positioned(

View file

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/entry/extensions/props.dart';
@ -15,7 +16,7 @@ import 'package:leak_tracker/leak_tracker.dart';
class VideoConductor { class VideoConductor {
final CollectionLens? _collection; final CollectionLens? _collection;
final List<AvesVideoController> _controllers = []; final List<AvesVideoController> _controllers = [];
final List<StreamSubscription> _subscriptions = []; final Map<AvesVideoController, StreamSubscription> _subscriptions = {};
final PlaybackStateHandler _playbackStateHandler = DatabasePlaybackStateHandler(); final PlaybackStateHandler _playbackStateHandler = DatabasePlaybackStateHandler();
final ValueNotifier<AvesVideoController?> playingVideoControllerNotifier = ValueNotifier(null); final ValueNotifier<AvesVideoController?> playingVideoControllerNotifier = ValueNotifier(null);
@ -36,9 +37,6 @@ class VideoConductor {
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
LeakTracking.dispatchObjectDisposed(object: this); LeakTracking.dispatchObjectDisposed(object: this);
} }
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
await _disposeAll(); await _disposeAll();
playingVideoControllerNotifier.dispose(); playingVideoControllerNotifier.dispose();
_controllers.clear(); _controllers.clear();
@ -47,22 +45,24 @@ class VideoConductor {
} }
} }
AvesVideoController getOrCreateController(AvesEntry entry, {int? maxControllerCount}) { Future<AvesVideoController> getOrCreateController(AvesEntry entry, {int? maxControllerCount}) async {
var controller = getController(entry); var controller = getController(entry);
if (controller != null) { if (controller != null) {
_controllers.remove(controller); _controllers.remove(controller);
} else { } else {
maxControllerCount = max(_defaultMaxControllerCount, maxControllerCount ?? 0);
while (_controllers.length >= maxControllerCount) {
await _disposeController(_controllers.removeLast());
}
await deviceService.requestGarbageCollection();
controller = videoControllerFactory.buildController( controller = videoControllerFactory.buildController(
entry, entry,
playbackStateHandler: _playbackStateHandler, playbackStateHandler: _playbackStateHandler,
settings: settings, settings: settings,
); );
_subscriptions.add(controller.statusStream.listen((event) => _onControllerStatusChanged(entry, controller!, event))); _subscriptions[controller] = controller.statusStream.listen((event) => _onControllerStatusChanged(entry, controller!, event));
} }
_controllers.insert(0, controller); _controllers.insert(0, controller);
while (_controllers.length > (maxControllerCount ?? _defaultMaxControllerCount)) {
_controllers.removeLast().dispose();
}
return controller; return controller;
} }
@ -99,9 +99,14 @@ class VideoConductor {
Future<void> _applyToAll(FutureOr Function(AvesVideoController controller) action) => Future.forEach<AvesVideoController>(_controllers, action); Future<void> _applyToAll(FutureOr Function(AvesVideoController controller) action) => Future.forEach<AvesVideoController>(_controllers, action);
Future<void> _disposeAll() => _applyToAll((controller) => controller.dispose()); Future<void> _disposeAll() => _applyToAll(_disposeController);
Future<void> pauseAll() => _applyToAll((controller) => controller.pause()); Future<void> pauseAll() => _applyToAll((controller) => controller.pause());
Future<void> muteAll(bool muted) => _applyToAll((controller) => controller.mute(muted)); Future<void> muteAll(bool muted) => _applyToAll((controller) => controller.mute(muted));
Future<void> _disposeController(AvesVideoController controller) async {
await _subscriptions.remove(controller)?.cancel();
await controller.dispose();
}
} }

View file

@ -130,7 +130,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
} }
Future<void> _initVideoController(AvesEntry entry) async { Future<void> _initVideoController(AvesEntry entry) async {
final controller = context.read<VideoConductor>().getOrCreateController(entry); final controller = await context.read<VideoConductor>().getOrCreateController(entry);
setState(() {}); setState(() {});
if (videoAutoPlayEnabled || entry.isAnimated) { if (videoAutoPlayEnabled || entry.isAnimated) {
@ -157,7 +157,9 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
if (videoPageEntries.isNotEmpty) { if (videoPageEntries.isNotEmpty) {
// init video controllers for all pages that could need it // init video controllers for all pages that could need it
final videoConductor = context.read<VideoConductor>(); final videoConductor = context.read<VideoConductor>();
videoPageEntries.forEach((entry) => videoConductor.getOrCreateController(entry, maxControllerCount: videoPageEntries.length)); await Future.forEach(videoPageEntries, (entry) async {
await videoConductor.getOrCreateController(entry, maxControllerCount: videoPageEntries.length);
});
// auto play/pause when changing page // auto play/pause when changing page
Future<void> _onPageChanged() async { Future<void> _onPageChanged() async {

View file

@ -43,6 +43,8 @@ class _RasterImageViewState extends State<RasterImageView> {
final ValueNotifier<bool> _fullImageLoaded = ValueNotifier(false); final ValueNotifier<bool> _fullImageLoaded = ValueNotifier(false);
ImageInfo? _fullImageInfo; ImageInfo? _fullImageInfo;
static const double _tilesByShortestSide = 2;
AvesEntry get entry => widget.entry; AvesEntry get entry => widget.entry;
ValueNotifier<ViewState> get viewStateNotifier => widget.viewStateNotifier; ValueNotifier<ViewState> get viewStateNotifier => widget.viewStateNotifier;
@ -61,7 +63,7 @@ class _RasterImageViewState extends State<RasterImageView> {
region: fullImageRegion, region: fullImageRegion,
); );
} else { } else {
return entry.uriImage; return entry.fullImage;
} }
} }
@ -158,7 +160,7 @@ class _RasterImageViewState extends State<RasterImageView> {
void _initTiling(Size viewportSize) { void _initTiling(Size viewportSize) {
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
_tileSide = viewportSize.shortestSide * devicePixelRatio; _tileSide = viewportSize.shortestSide * devicePixelRatio / _tilesByShortestSide;
// scale for initial state `contained` // scale for initial state `contained`
final containedScale = min(viewportSize.width / _displaySize.width, viewportSize.height / _displaySize.height); final containedScale = min(viewportSize.width / _displaySize.width, viewportSize.height / _displaySize.height);
_maxSampleSize = ExtraAvesEntryImages.sampleSizeForScale(magnifierScale: containedScale, devicePixelRatio: devicePixelRatio); _maxSampleSize = ExtraAvesEntryImages.sampleSizeForScale(magnifierScale: containedScale, devicePixelRatio: devicePixelRatio);

View file

@ -58,7 +58,7 @@ class _VideoCoverState extends State<VideoCover> {
Size get videoDisplaySize => widget.videoDisplaySize; Size get videoDisplaySize => widget.videoDisplaySize;
// use the high res photo as cover for the video part of a motion photo // use the high res photo as cover for the video part of a motion photo
ImageProvider get videoCoverUriImage => (mainEntry.isMotionPhoto ? mainEntry : entry).uriImage; ImageProvider get videoCoverUriImage => (mainEntry.isMotionPhoto ? mainEntry : entry).fullImage;
@override @override
void initState() { void initState() {

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5" sha256: de9ecbb3ddafd446095f7e833c853aff2fa1682b017921fe63a833f9d6f0e422
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.53" version: "1.3.54"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.4" version: "4.0.5"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -296,10 +296,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core name: firebase_core
sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b sha256: "017d17d9915670e6117497e640b2859e0b868026ea36bf3a57feb28c3b97debe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.12.1" version: "3.13.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -312,26 +312,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493 sha256: "129a34d1e0fb62e2b488d988a1fc26cc15636357e50944ffee2862efe8929b23"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.21.1" version: "2.22.0"
firebase_crashlytics: firebase_crashlytics:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics name: firebase_crashlytics
sha256: d672dad83e6e99b826599fef63dbe71bac70633d5c3df90c124e986e1461e79b sha256: f3fa4a17c2f061b16b2e3ac7aaed889ae954b8952d0fd3e0009a9870cde7bbd2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.4" version: "4.3.5"
firebase_crashlytics_platform_interface: firebase_crashlytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics_platform_interface name: firebase_crashlytics_platform_interface
sha256: b2468a5cd54051dd31ca332a5c35f1bcbfb21b0135f84d4606c3275a226c0321 sha256: cedfbe39927711c0e56fc38bfecbd89e17816b21698a3d88d63298c530ed375c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.8.4" version: "3.8.5"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -352,10 +352,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flex_seed_scheme name: flex_seed_scheme
sha256: d3ba3c5c92d2d79d45e94b4c6c71d01fac3c15017da1545880c53864da5dfeb0 sha256: b06d8b367b84cbf7ca5c5603c858fa5edae88486c4e4da79ac1044d73b6c62ec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.0" version: "3.5.1"
floating: floating:
dependency: "direct main" dependency: "direct main"
description: description:
@ -440,10 +440,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5 sha256: "634622a3a826d67cb05c0e3e576d1812c430fa98404e95b60b131775c73d76ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.6+2" version: "0.7.7"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -535,26 +535,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter name: google_maps_flutter
sha256: "621125e35e81ca39ef600e45243d2be93167e61def72bc7207b0c4a635c58506" sha256: "830d8f7b51b4a950bf0d7daa675324fed6c9beb57a7ecca2a59018270c96b4e0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.10.1" version: "2.12.1"
google_maps_flutter_android: google_maps_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_android name: google_maps_flutter_android
sha256: "3b3f55d6b4f2bde6bbe80dca0bf8d228313005c9ce8a97a1d24257600d8c92e5" sha256: "0ede4ae8326335c0c007c8c7a8c9737449263123385e2bdf49f3e71103b2dc2e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.14.14" version: "2.16.0"
google_maps_flutter_ios: google_maps_flutter_ios:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_ios name: google_maps_flutter_ios
sha256: "6f798adb0aa1db5adf551f2e39e24bd06c8c0fbe4de912fb2d9b5b3f48147b02" sha256: ef72c822930ce69515cb91c10cd88cfb8b26296f765808a43cbc9a10eaffacfe
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.2" version: "2.15.0"
google_maps_flutter_platform_interface: google_maps_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -567,10 +567,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_web name: google_maps_flutter_web
sha256: bbeb93807a34bfeebdb7ace506bd2bc400a3915dc96736254fea721eb264caa0 sha256: a45786ea6691cc7cdbe2cf3ce2c2daf4f82a885745666b4a36baada3a4e12897
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.11" version: "0.5.12"
gpx: gpx:
dependency: "direct main" dependency: "direct main"
description: description:
@ -623,10 +623,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.3" version: "4.5.4"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -788,31 +788,29 @@ packages:
source: hosted source: hosted
version: "7.0.7296" version: "7.0.7296"
media_kit: media_kit:
dependency: "direct overridden" dependency: transitive
description: description:
path: media_kit name: media_kit
ref: d2145a50f68394096845915a28874341fbf5b3fe sha256: "48c10c3785df5d88f0eef970743f8c99b2e5da2b34b9d8f9876e598f62d9e776"
resolved-ref: d2145a50f68394096845915a28874341fbf5b3fe url: "https://pub.dev"
url: "https://github.com/media-kit/media-kit.git" source: hosted
source: git version: "1.2.0"
version: "1.1.11"
media_kit_libs_android_video: media_kit_libs_android_video:
dependency: transitive dependency: transitive
description: description:
name: media_kit_libs_android_video name: media_kit_libs_android_video
sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c" sha256: adff9b571b8ead0867f9f91070f8df39562078c0eb3371d88b9029a2d547d7b7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.6" version: "1.3.7"
media_kit_video: media_kit_video:
dependency: "direct overridden" dependency: transitive
description: description:
path: media_kit_video name: media_kit_video
ref: d2145a50f68394096845915a28874341fbf5b3fe sha256: a656a9463298c1adc64c57f2d012874f7f2900f0c614d9545a3e7b8bb9e2137b
resolved-ref: d2145a50f68394096845915a28874341fbf5b3fe url: "https://pub.dev"
url: "https://github.com/media-kit/media-kit.git" source: hosted
source: git version: "1.3.0"
version: "1.2.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -1171,10 +1169,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.4"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -1267,10 +1265,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@ -1577,10 +1575,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.2" version: "6.3.3"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@ -1673,18 +1671,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: volume_controller name: volume_controller
sha256: "30863a51338db47fe16f92902b1a6c4ee5e15c9287b46573d7c2eb6be1f197d2" sha256: e82fd689bb8e1fe8e64be3fa5946ff8699058f8cf9f4c1679acdba20cda7f5bd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.1" version: "3.3.3"
wakelock_plus: wakelock_plus:
dependency: transitive dependency: transitive
description: description:
name: wakelock_plus name: wakelock_plus
sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e" sha256: b90fbcc8d7bdf3b883ea9706d9d76b9978cb1dfa4351fcc8014d6ec31a493354
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.10" version: "1.2.11"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:

View file

@ -7,7 +7,7 @@ repository: https://github.com/deckerst/aves
# - play changelog: /whatsnew/whatsnew-en-US # - play changelog: /whatsnew/whatsnew-en-US
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt # - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
version: 1.12.8+148 version: 1.12.9+149
publish_to: none publish_to: none
environment: environment:
@ -130,18 +130,6 @@ dependencies:
volume_controller: volume_controller:
xml: xml:
dependency_overrides:
media_kit:
git:
url: https://github.com/media-kit/media-kit.git
ref: d2145a50f68394096845915a28874341fbf5b3fe
path: media_kit
media_kit_video:
git:
url: https://github.com/media-kit/media-kit.git
ref: d2145a50f68394096845915a28874341fbf5b3fe
path: media_kit_video
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@ -171,9 +159,6 @@ flutter:
################################################################################ ################################################################################
# Test driver # Test driver
# capture shaders (profile mode, real device only):
# % ./flutterw drive --flavor play -t test_driver/driver_shaders.dart --profile --cache-sksl --write-sksl-on-exit shaders.sksl.json
# generate screenshots (profile mode, specific collection): # generate screenshots (profile mode, specific collection):
# % ./flutterw drive --flavor play -t test_driver/driver_screenshots.dart --profile # % ./flutterw drive --flavor play -t test_driver/driver_screenshots.dart --profile

File diff suppressed because one or more lines are too long

View file

@ -58,6 +58,8 @@ Future<void> configureAndLaunch() async {
..coordinateFormat = CoordinateFormat.dms ..coordinateFormat = CoordinateFormat.dms
..unitSystem = UnitSystem.metric ..unitSystem = UnitSystem.metric
// map // map
..mapStyle = EntryMapStyle.googleNormal; ..mapStyle = EntryMapStyle.googleNormal
// debug
..debugShowViewerTiles = false;
app.main(); app.main();
} }

View file

@ -1,4 +1,4 @@
In v1.12.8: In v1.12.9:
- play more kinds of motion photos - play more kinds of motion photos
- enjoy the app in Galician - enjoy the app in Galician and Kannada
Full changelog available on GitHub Full changelog available on GitHub