Compare commits

..

14 commits

Author SHA1 Message Date
9037f8e610 Aggiorna README.md
Some checks failed
Quality check / Flutter analysis (push) Has been cancelled
Quality check / CodeQL analysis (java-kotlin) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
2025-08-14 17:41:59 +08:00
05bb77a793 Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:51:39 +08:00
2f0f3da2fa Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:44:51 +08:00
31f85c3e01 Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:41:04 +08:00
84a822022a Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-08-14 16:07:15 +08:00
94c83914a4 Aggiorna README.md
Some checks failed
Quality check / Flutter analysis (push) Has been cancelled
Quality check / CodeQL analysis (java-kotlin) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
2025-06-11 16:40:56 +08:00
a461e2c55f Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-06-11 16:40:17 +08:00
99c9f85eaf Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-06-11 16:39:24 +08:00
848ad5220e Aggiorna README.md
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2025-06-11 16:38:06 +08:00
Thibault Deckers
7577466978 rescale large thumbnails decoded as is;
check thumbnail bitmap size before getting raw bytes
2025-06-10 20:45:59 +02:00
Thibault Deckers
dfcaf4d35a upgrades 2025-06-09 18:47:11 +02:00
Thibault Deckers
171394056f #1608 query bar unfocus when navigating away 2025-06-07 20:12:54 +02:00
dependabot[bot]
60211545e1
Bump github/codeql-action from 3.28.18 to 3.28.19 (#1604)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.28.19.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ff0a06e83c...fca7ace96b)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-08 01:03:54 +09:00
Thibault Deckers
edbf9744f5 #1612 info: show matching dynamic albums 2025-06-07 18:03:32 +02:00
21 changed files with 201 additions and 70 deletions

View file

@ -72,7 +72,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@ -86,6 +86,6 @@ jobs:
./flutterw build apk --profile -t lib/main_play.dart --flavor play ./flutterw build apk --profile -t lib/main_play.dart --flavor play
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View file

@ -71,6 +71,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View file

@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased] ## <a id="unreleased"></a>[Unreleased]
### Added
- Info: show matching dynamic albums
### Fixed
- crash when decoding some large thumbnails
## <a id="v1.13.2"></a>[v1.13.2] - 2025-06-02 ## <a id="v1.13.2"></a>[v1.13.2] - 2025-06-02
### Changed ### Changed

View file

@ -111,17 +111,96 @@ Some users have expressed the wish to financially support the project. Thanks!
## Project Setup ## Project Setup
### Install dependencies
Before running or building the app, update the dependencies for the desired flavor: Before running or building the app, update the dependencies for the desired flavor:
``` ```
# scripts/apply_flavor_play.sh scripts/apply_flavor_play.sh
``` ```
To build the project, create a file named `<app dir>/android/key.properties`. It should contain a reference to a keystore for app signing, and other necessary credentials. See [key_template.properties](https://github.com/deckerst/aves/blob/develop/android/key_template.properties) for the expected keys. To build the project, create a file named `<app dir>/android/key.properties`. It should contain a reference to a keystore for app signing, and other necessary credentials. See [key_template.properties](https://github.com/deckerst/aves/blob/develop/android/key_template.properties) for the expected keys.
To run the app: ### To run the app:
``` ```
# ./flutterw run -t lib/main_play.dart --flavor play ./flutterw run -t lib/main_play.dart --flavor play
```
### To build the app:
creare file con le tue credenziali file.keystore
dove YOUR_ALIAS_NAME è il tuo unico alias name
e YOUR_ALIAS_PWD è la password del tuo alias
```sh
keytool -genkey -v -keystore file.keystore -alias YOUR_ALIAS_NAME -storepass YOUR_ALIAS_PWD -keypass YOUR_ALIAS_PWD -keyalg RSA -validity 36500
```
in questo caso ho inserito
```sh
cd android
keytool -genkey -v -keystore file.keystore -alias FabioMich66 -storepass Master66 -keypass Master66 -keyalg RSA -validity 36500
```
se non puoi eseguire keytool perchè non è nel path di sistema cercalo usando
```sh
cd /
sudo find -name keytool
```
compilare il file `<app dir>/android/key.properties`
```
nano android/key.properties
```
questi i miei dati utilizzando il format key_template.properties
```
storeFile=/Users/fabio/flutter_apps/aves/android/file.keystore
storePassword=Master66
keyAlias=FabioMich66
keyPassword=Master66
googleApiKey=<GOOGLE_API_KEY>
```
infine compilare l'apk
```
./flutterw build apk -t lib/main_play.dart --flavor play
``` ```
[Version badge]: https://img.shields.io/github/v/release/deckerst/aves?include_prereleases&sort=semver [Version badge]: https://img.shields.io/github/v/release/deckerst/aves?include_prereleases&sort=semver
[Build badge]: https://img.shields.io/github/actions/workflow/status/deckerst/aves/quality-check.yml?branch=develop [Build badge]: https://img.shields.io/github/actions/workflow/status/deckerst/aves/quality-check.yml?branch=develop
## Android studio
caricare il file da github selezionando le mnù a tendina File-New-project from Version Control
selezionare version control tipo: git
inserire URL di aves
https://github.com/deckerst/aves
flaggare shallow clone with history troncated 1 commits
aprire la console sulla dir aves appena creata e caricare le dipendenze
```
scripts/apply_flavor_izzy.sh
```
in settings - Languages and Framework - Dart inserire il path
```
/home/fabio/flutter/bin/cache/
```
e spuntare project aves
Edit configurations e aggiungere shell script con un nome x es izzi
poi flaggare script text e inserire
./flutterw run -t lib/main_izzy.dart --flavor izzy
la working directory sarà una cosa così
/home/fabio/StudioProjects/aves

View file

@ -33,13 +33,13 @@ kotlin {
} }
android { android {
namespace 'deckers.thibault.aves' namespace = 'deckers.thibault.aves'
compileSdk 35 compileSdk = 36
defaultConfig { defaultConfig {
applicationId packageName applicationId packageName
minSdk flutter.minSdkVersion minSdk flutter.minSdkVersion
targetSdk 35 targetSdk 36
versionCode flutter.versionCode versionCode flutter.versionCode
versionName flutter.versionName versionName flutter.versionName
manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>"] manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>"]
@ -149,14 +149,14 @@ repositories {
} }
dependencies { dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2'
implementation "androidx.appcompat:appcompat:1.7.0" implementation "androidx.appcompat:appcompat:1.7.1"
implementation 'androidx.core:core-ktx:1.16.0' implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.lifecycle:lifecycle-process:2.9.0' implementation 'androidx.lifecycle:lifecycle-process:2.9.1'
implementation 'androidx.media:media:1.7.0' implementation 'androidx.media:media:1.7.0'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha07' implementation 'androidx.security:security-crypto:1.1.0-beta01'
implementation 'androidx.work:work-runtime-ktx:2.10.1' implementation 'androidx.work:work-runtime-ktx:2.10.1'
implementation 'com.commonsware.cwac:document:0.5.0' implementation 'com.commonsware.cwac:document:0.5.0'
@ -164,7 +164,7 @@ dependencies {
implementation "com.github.bumptech.glide:glide:$glide_version" implementation "com.github.bumptech.glide:glide:$glide_version"
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
// SLF4J implementation for `mp4parser` // SLF4J implementation for `mp4parser`
implementation 'org.slf4j:slf4j-simple:2.0.16' implementation 'org.slf4j:slf4j-simple:2.0.17'
// forked, built by JitPack: // forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory // - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
@ -178,7 +178,7 @@ dependencies {
implementation 'com.github.deckerst:pixymeta-android:cb1cdc932e' implementation 'com.github.deckerst:pixymeta-android:cb1cdc932e'
implementation project(':exifinterface') implementation project(':exifinterface')
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.11.4' testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.13.1'
kapt 'androidx.annotation:annotation:1.9.1' kapt 'androidx.annotation:annotation:1.9.1'
ksp "com.github.bumptech.glide:ksp:$glide_version" ksp "com.github.bumptech.glide:ksp:$glide_version"

View file

@ -2,6 +2,7 @@ package deckers.thibault.aves.channel.calls
import android.app.ActivityManager import android.app.ActivityManager
import android.content.Context import android.content.Context
import androidx.core.content.edit
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo import androidx.work.WorkInfo
@ -18,7 +19,6 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class AnalysisHandler(private val activity: FlutterFragmentActivity, private val onAnalysisCompleted: () -> Unit) : MethodChannel.MethodCallHandler { class AnalysisHandler(private val activity: FlutterFragmentActivity, private val onAnalysisCompleted: () -> Unit) : MethodChannel.MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@ -38,9 +38,8 @@ class AnalysisHandler(private val activity: FlutterFragmentActivity, private val
} }
val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
with(preferences.edit()) { preferences.edit {
putLong(AnalysisWorker.PREF_CALLBACK_HANDLE_KEY, callbackHandle) putLong(AnalysisWorker.PREF_CALLBACK_HANDLE_KEY, callbackHandle)
apply()
} }
result.success(true) result.success(true)
} }
@ -69,9 +68,8 @@ class AnalysisHandler(private val activity: FlutterFragmentActivity, private val
// work `Data` cannot occupy more than 10240 bytes when serialized // work `Data` cannot occupy more than 10240 bytes when serialized
// so we save the possibly long list of entry IDs to shared preferences // so we save the possibly long list of entry IDs to shared preferences
val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
with(preferences.edit()) { preferences.edit {
putStringSet(AnalysisWorker.PREF_ENTRY_IDS_KEY, allEntryIds?.map { it.toString() }?.toSet()) putStringSet(AnalysisWorker.PREF_ENTRY_IDS_KEY, allEntryIds?.map { it.toString() }?.toSet())
apply()
} }
val workData = workDataOf( val workData = workDataOf(

View file

@ -1,5 +1,6 @@
package deckers.thibault.aves.channel.calls package deckers.thibault.aves.channel.calls
import android.annotation.SuppressLint
import android.app.LocaleConfig import android.app.LocaleConfig
import android.app.LocaleManager import android.app.LocaleManager
import android.content.Context import android.content.Context
@ -102,6 +103,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
@SuppressLint("WrongConstant")
val lm = context.getSystemService(Context.LOCALE_SERVICE) as? LocaleManager val lm = context.getSystemService(Context.LOCALE_SERVICE) as? LocaleManager
lm?.overrideLocaleConfig = LocaleConfig(LocaleList.forLanguageTags(locales.joinToString(","))) lm?.overrideLocaleConfig = LocaleConfig(LocaleList.forLanguageTags(locales.joinToString(",")))
} }

View file

@ -31,7 +31,7 @@ class GeocodingHandler(private val context: Context) : MethodCallHandler {
private fun getAddress(call: MethodCall, result: MethodChannel.Result) { private fun getAddress(call: MethodCall, result: MethodChannel.Result) {
val latitude = call.argument<Number>("latitude")?.toDouble() val latitude = call.argument<Number>("latitude")?.toDouble()
val longitude = call.argument<Number>("longitude")?.toDouble() val longitude = call.argument<Number>("longitude")?.toDouble()
val localeString = call.argument<String>("locale") val localeLanguageTag = call.argument<String>("localeLanguageTag")
val maxResults = call.argument<Int>("maxResults") ?: 1 val maxResults = call.argument<Int>("maxResults") ?: 1
if (latitude == null || longitude == null) { if (latitude == null || longitude == null) {
result.error("getAddress-args", "missing arguments", null) result.error("getAddress-args", "missing arguments", null)
@ -43,11 +43,8 @@ class GeocodingHandler(private val context: Context) : MethodCallHandler {
return return
} }
geocoder = geocoder ?: if (localeString != null) { geocoder = geocoder ?: if (localeLanguageTag != null) {
val split = localeString.split("_") Geocoder(context, Locale.forLanguageTag(localeLanguageTag))
val language = split[0]
val country = if (split.size > 1) split[1] else ""
Geocoder(context, Locale(language, country))
} else { } else {
Geocoder(context) Geocoder(context)
} }

View file

@ -1,6 +1,7 @@
package deckers.thibault.aves.channel.calls package deckers.thibault.aves.channel.calls
import android.content.Context import android.content.Context
import androidx.core.content.edit
import deckers.thibault.aves.SearchSuggestionsProvider import deckers.thibault.aves.SearchSuggestionsProvider
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
@ -29,9 +30,8 @@ class GlobalSearchHandler(private val context: Context) : MethodCallHandler {
} }
val preferences = context.getSharedPreferences(SearchSuggestionsProvider.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) val preferences = context.getSharedPreferences(SearchSuggestionsProvider.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
with(preferences.edit()) { preferences.edit {
putLong(SearchSuggestionsProvider.CALLBACK_HANDLE_KEY, callbackHandle) putLong(SearchSuggestionsProvider.CALLBACK_HANDLE_KEY, callbackHandle)
apply()
} }
result.success(true) result.success(true)
} }

View file

@ -2,6 +2,7 @@ package deckers.thibault.aves.channel.calls
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey import androidx.security.crypto.MasterKey
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
@ -45,7 +46,7 @@ class SecurityHandler(private val context: Context) : MethodCallHandler {
} }
val preferences = getStore() val preferences = getStore()
with(preferences.edit()) { preferences.edit {
when (value) { when (value) {
is Boolean -> putBoolean(key, value) is Boolean -> putBoolean(key, value)
is Float -> putFloat(key, value) is Float -> putFloat(key, value)
@ -58,7 +59,6 @@ class SecurityHandler(private val context: Context) : MethodCallHandler {
return return
} }
} }
apply()
} }
result.success(true) result.success(true)
} }

View file

@ -5,8 +5,10 @@ import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log
import android.util.Size import android.util.Size
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.graphics.scale
import androidx.core.net.toUri import androidx.core.net.toUri
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.DecodeFormat
@ -17,6 +19,7 @@ import deckers.thibault.aves.decoder.AvesAppGlideModule
import deckers.thibault.aves.decoder.MultiPageImage import deckers.thibault.aves.decoder.MultiPageImage
import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.SVG import deckers.thibault.aves.utils.MimeTypes.SVG
import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.MimeTypes.isVideo
@ -25,6 +28,8 @@ import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
import deckers.thibault.aves.utils.StorageUtils import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.UriUtils.tryParseId import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlin.math.min
import kotlin.math.roundToInt
class ThumbnailFetcher internal constructor( class ThumbnailFetcher internal constructor(
private val context: Context, private val context: Context,
@ -77,6 +82,29 @@ class ThumbnailFetcher internal constructor(
} }
} }
if (bitmap != null) {
if (bitmap.width > width && bitmap.height > height) {
val scalingFactor: Double = min(bitmap.width.toDouble() / width, bitmap.height.toDouble() / height)
val dstWidth = (bitmap.width / scalingFactor).roundToInt()
val dstHeight = (bitmap.height / scalingFactor).roundToInt()
Log.d(
LOG_TAG, "rescale thumbnail for mimeType=$mimeType uri=$uri width=$width height=$height" +
", with bitmap byteCount=${bitmap.byteCount} size=${bitmap.width}x${bitmap.height}" +
", to target=${dstWidth}x${dstHeight}"
)
bitmap = bitmap.scale(dstWidth, dstHeight)
}
if (bitmap.byteCount > BITMAP_SIZE_DANGER_THRESHOLD) {
result.error(
"getThumbnail-large", "thumbnail bitmap dangerously large" +
" for mimeType=$mimeType uri=$uri pageId=$pageId width=$width height=$height" +
", with bitmap byteCount=${bitmap.byteCount} size=${bitmap.width}x${bitmap.height} config=${bitmap.config?.name}", null
)
return
}
}
// do not recycle bitmaps fetched from `ContentResolver` or Glide as their lifecycle is unknown // do not recycle bitmaps fetched from `ContentResolver` or Glide as their lifecycle is unknown
val recycle = false val recycle = false
val bytes = BitmapUtils.getRawBytes(bitmap, recycle = recycle) val bytes = BitmapUtils.getRawBytes(bitmap, recycle = recycle)
@ -144,4 +172,9 @@ class ThumbnailFetcher internal constructor(
Glide.with(context).clear(target) Glide.with(context).clear(target)
} }
} }
companion object {
private val LOG_TAG = LogUtils.createTag<ThumbnailFetcher>()
private const val BITMAP_SIZE_DANGER_THRESHOLD = 20 * (1 shl 20) // MB
}
} }

View file

@ -81,12 +81,12 @@ object PixyMetaHelper {
output: OutputStream, output: OutputStream,
iptcDataList: List<FieldMap>?, iptcDataList: List<FieldMap>?,
) { ) {
val iptc = iptcDataList?.flatMap { val iptc: List<IPTCDataSet> = iptcDataList?.flatMap {
val record = it["record"] as Int val record = it["record"] as Int
val tag = it["tag"] as Int val tag = it["tag"] as Int
val values = it["values"] as List<*> val values = it["values"] as List<*>
values.map { data -> IPTCDataSet(IPTCRecord.fromRecordNumber(record), tag, data as ByteArray) } values.map { data -> IPTCDataSet(IPTCRecord.fromRecordNumber(record), tag, data as ByteArray) }
} ?: ArrayList<IPTCDataSet>() } ?: ArrayList()
Metadata.insertIPTC(input, output, iptc) Metadata.insertIPTC(input, output, iptc)
} }

View file

@ -5,5 +5,5 @@ import kotlin.math.pow
object MathUtils { object MathUtils {
fun highestPowerOf2(x: Int): Int = highestPowerOf2(x.toDouble()) fun highestPowerOf2(x: Int): Int = highestPowerOf2(x.toDouble())
private fun highestPowerOf2(x: Double): Int = if (x < 1) 0 else 2.toDouble().pow(log2(x).toInt()).toInt() fun highestPowerOf2(x: Double): Int = if (x < 1) 0 else 2.toDouble().pow(log2(x).toInt()).toInt()
} }

View file

@ -18,10 +18,10 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.8.1" apply false id("com.android.application") version "8.10.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.10" apply false id("org.jetbrains.kotlin.android") version "2.1.21" apply false
id("com.google.devtools.ksp") version "2.1.10-1.0.29" apply false id("com.google.devtools.ksp") version "2.1.21-2.0.1" apply false
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
} }
include(":app") include(":app")

View file

@ -15,7 +15,7 @@ class GeocodingService {
final result = await _platform.invokeMethod('getAddress', <String, dynamic>{ final result = await _platform.invokeMethod('getAddress', <String, dynamic>{
'latitude': coordinates.latitude, 'latitude': coordinates.latitude,
'longitude': coordinates.longitude, 'longitude': coordinates.longitude,
'locale': locale.toString(), 'localeLanguageTag': locale.toLanguageTag(),
// we only really need one address, but sometimes the native geocoder // we only really need one address, but sometimes the native geocoder
// returns nothing with `maxResults` of 1, but succeeds with `maxResults` of 2+ // returns nothing with `maxResults` of 1, but succeeds with `maxResults` of 2+
'maxResults': 2, 'maxResults': 2,

View file

@ -247,7 +247,7 @@ class PlatformMediaFetchService implements MediaFetchService {
return InteropDecoding.bytesToCodec(bytes); return InteropDecoding.bytesToCodec(bytes);
} }
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
if (_isUnknownVisual(mimeType)) { if (_isUnknownVisual(mimeType) || e.code == 'getThumbnail-large') {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
} }

View file

@ -18,6 +18,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/collection/entry_set_action_delegate.dart'; import 'package:aves/widgets/collection/entry_set_action_delegate.dart';
import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/collection/filter_bar.dart';
@ -56,7 +57,7 @@ class CollectionAppBar extends StatefulWidget {
State<CollectionAppBar> createState() => _CollectionAppBarState(); State<CollectionAppBar> createState() => _CollectionAppBarState();
} }
class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerProviderStateMixin, WidgetsBindingObserver { class _CollectionAppBarState extends State<CollectionAppBar> with RouteAware, SingleTickerProviderStateMixin, WidgetsBindingObserver {
final Set<StreamSubscription> _subscriptions = {}; final Set<StreamSubscription> _subscriptions = {};
final EntrySetActionDelegate _actionDelegate = EntrySetActionDelegate(); final EntrySetActionDelegate _actionDelegate = EntrySetActionDelegate();
late AnimationController _browseToSelectAnimation; late AnimationController _browseToSelectAnimation;
@ -122,6 +123,15 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
}); });
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
final route = ModalRoute.of(context);
if (route is PageRoute) {
AvesApp.pageRouteObserver.subscribe(this, route);
}
}
@override @override
void didUpdateWidget(covariant CollectionAppBar oldWidget) { void didUpdateWidget(covariant CollectionAppBar oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
@ -140,6 +150,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
AvesApp.pageRouteObserver.unsubscribe(this);
super.dispose(); super.dispose();
} }
@ -151,6 +162,13 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
widget.collection.filterChangeNotifier.removeListener(_onFilterChanged); widget.collection.filterChangeNotifier.removeListener(_onFilterChanged);
} }
@override
void didPushNext() {
// unfocus when navigating away, so that when navigating back,
// the query bar does not get back focus and bring the keyboard
_queryBarFocusNode.unfocus();
}
@override @override
void didChangeMetrics() { void didChangeMetrics() {
// when top padding or text scale factor change // when top padding or text scale factor change

View file

@ -11,6 +11,7 @@ import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/action_controls/togglers/title_search.dart'; import 'package:aves/widgets/common/action_controls/togglers/title_search.dart';
import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart';
import 'package:aves/widgets/common/app_bar/app_bar_title.dart'; import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
@ -78,7 +79,7 @@ class FilterGridAppBar<T extends CollectionFilter, CSAD extends ChipSetActionDel
} }
} }
class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetActionDelegate<T>> extends State<FilterGridAppBar<T, CSAD>> with SingleTickerProviderStateMixin, WidgetsBindingObserver { class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetActionDelegate<T>> extends State<FilterGridAppBar<T, CSAD>> with RouteAware, SingleTickerProviderStateMixin, WidgetsBindingObserver {
final Set<StreamSubscription> _subscriptions = {}; final Set<StreamSubscription> _subscriptions = {};
late AnimationController _browseToSelectAnimation; late AnimationController _browseToSelectAnimation;
final ValueNotifier<bool> _isSelectingNotifier = ValueNotifier(false); final ValueNotifier<bool> _isSelectingNotifier = ValueNotifier(false);
@ -112,6 +113,15 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
WidgetsBinding.instance.addPostFrameCallback((_) => _updateAppBarHeight()); WidgetsBinding.instance.addPostFrameCallback((_) => _updateAppBarHeight());
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
final route = ModalRoute.of(context);
if (route is PageRoute) {
AvesApp.pageRouteObserver.subscribe(this, route);
}
}
@override @override
void dispose() { void dispose() {
_queryBarFocusNode.dispose(); _queryBarFocusNode.dispose();
@ -122,9 +132,17 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
AvesApp.pageRouteObserver.unsubscribe(this);
super.dispose(); super.dispose();
} }
@override
void didPushNext() {
// unfocus when navigating away, so that when navigating back,
// the query bar does not get back focus and bring the keyboard
_queryBarFocusNode.unfocus();
}
@override @override
void didChangeMetrics() { void didChangeMetrics() {
// when text scale factor changes // when text scale factor changes

View file

@ -1,6 +1,7 @@
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/image_providers/app_icon_image_provider.dart'; import 'package:aves/image_providers/app_icon_image_provider.dart';
import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/dynamic_albums.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/favourites.dart'; import 'package:aves/model/entry/extensions/favourites.dart';
import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/multipage.dart';
@ -133,6 +134,7 @@ class _BasicSectionState extends State<BasicSection> {
if (entry.isPureVideo && !entry.is360) MimeFilter.video, if (entry.isPureVideo && !entry.is360) MimeFilter.video,
if (dateTime != null) ...[DateFilter(DateLevel.ymd, dateTime.date), WeekDayFilter(dateTime.weekday)], if (dateTime != null) ...[DateFilter(DateLevel.ymd, dateTime.date), WeekDayFilter(dateTime.weekday)],
if (album != null) StoredAlbumFilter(album, collection?.source.getStoredAlbumDisplayName(context, album)), if (album != null) StoredAlbumFilter(album, collection?.source.getStoredAlbumDisplayName(context, album)),
...dynamicAlbums.all.where((v) => v.test(entry)).toSet(),
if (entry.rating != 0) RatingFilter(entry.rating), if (entry.rating != 0) RatingFilter(entry.rating),
...tags.map(TagFilter.new), ...tags.map(TagFilter.new),
}; };

View file

@ -209,7 +209,7 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> {
bitmapScaling: MapBitmapScaling.none, bitmapScaling: MapBitmapScaling.none,
), ),
position: _toServiceLatLng(dotLocation), position: _toServiceLatLng(dotLocation),
zIndex: 1, zIndexInt: 1,
) )
}, },
polylines: { polylines: {

View file

@ -112,14 +112,6 @@ packages:
url: "https://github.com/deckerst/flutter_google_charts.git" url: "https://github.com/deckerst/flutter_google_charts.git"
source: git source: git
version: "0.12.0" version: "0.12.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_config: cli_config:
dependency: transitive dependency: transitive
description: description:
@ -180,10 +172,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: coverage name: coverage
sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a" sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.14.0" version: "1.14.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -588,10 +580,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_platform_interface name: google_maps_flutter_platform_interface
sha256: "970c8f766c02909c7be282dea923c971f83a88adaf07f8871d0aacebc3b07bb2" sha256: f8293f072ed8b068b092920a72da6693aa8b3d62dc6b5c5f0bc44c969a8a776c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.1" version: "2.12.1"
google_maps_flutter_web: google_maps_flutter_web:
dependency: transitive dependency: transitive
description: description:
@ -680,14 +672,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.1" version: "0.7.1"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
latlong2: latlong2:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1234,14 +1218,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
qr: qr:
dependency: transitive dependency: transitive
description: description: