Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2024-09-01 01:32:32 +02:00
commit 5565ac08f7
70 changed files with 1472 additions and 418 deletions

@ -1 +1 @@
Subproject commit 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
Subproject commit 5874a72aa4c779a02553007c47dacbefba2374dc

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily

View file

@ -4,14 +4,24 @@ on:
push:
branches:
- develop
pull_request:
types: [ opened, synchronize, reopened ]
permissions:
contents: read
jobs:
build:
name: Check code quality.
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- name: Clone the repository.
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get packages for the Flutter project.
run: scripts/pub_get_all.sh

27
.github/workflows/dependency-review.yml vendored Normal file
View file

@ -0,0 +1,27 @@
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request,
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
# Once installed, if the workflow run is marked as required,
# PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: 'Dependency Review'
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4

View file

@ -10,13 +10,18 @@ jobs:
name: Build and release artifacts.
runs-on: ubuntu-latest
steps:
- uses: actions/setup-java@v4
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4.2.2
with:
distribution: 'zulu'
java-version: '17'
- name: Clone the repository.
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get packages for the Flutter project.
run: scripts/pub_get_all.sh
@ -66,14 +71,14 @@ jobs:
AVES_GOOGLE_API_KEY: ${{ secrets.AVES_GOOGLE_API_KEY }}
- name: Create a release with the APK and App Bundle.
uses: ncipollo/release-action@v1
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
with:
artifacts: "outputs/*"
body: "[Changelog](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ github.ref_name }})"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: appbundle
path: outputs/app-play-release.aab
@ -83,15 +88,20 @@ jobs:
needs: [ build ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get appbundle from artifacts.
uses: actions/download-artifact@v4
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: appbundle
- name: Release app to beta channel.
uses: r0adkll/upload-google-play@v1.1.3
uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb # v1.1.3
with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: deckers.thibault.aves

76
.github/workflows/scorecards.yml vendored Normal file
View file

@ -0,0 +1,76 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '20 7 * * 2'
push:
branches: ["develop"]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
contents: read
actions: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecards on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
with:
sarif_file: results.sarif

18
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,18 @@
repos:
- repo: https://github.com/gherynos/pre-commit-java
rev: v0.2.4
hooks:
- id: Checkstyle
- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.3
hooks:
- id: gitleaks
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace

View file

@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
## <a id="v1.11.10"></a>[v1.11.10] - 2024-09-01
### Added
- Swedish translation (thanks Shift18, Andreas Håll)
### Changed
- request notification permission when launching scanning service
- upgraded Flutter to stable v3.24.1
### Fixed
- duplicates from new item loading/refreshing
## <a id="v1.11.9"></a>[v1.11.9] - 2024-08-07
### Added

View file

@ -76,7 +76,7 @@
-->
<uses-sdk tools:overrideLibrary="com.arthenica.ffmpegkit.flutter" />
<!-- from Android 11, we should define <queries> to make other apps visible to this app -->
<!-- from Android 11 (API 30), we should define <queries> to make other apps visible to this app -->
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
@ -92,7 +92,7 @@
<data android:mimeType="video/*" />
</intent>
<!--
from Android 11, `url_launcher` method `canLaunchUrl()` will return false,
from Android 11 (API 30), `url_launcher` method `canLaunchUrl()` will return false,
if appropriate intents are not declared, cf https://pub.dev/packages/url_launcher#configuration=
-->
<!-- to open https URLs -->

View file

@ -96,7 +96,7 @@ object PermissionManager {
segments.volumePath?.let { volumePath ->
val dirSet = dirsPerVolume[volumePath] ?: HashSet()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// request primary directory on volume from Android 11
// request primary directory on volume from Android 11 (API 30)
val relativeDir = segments.relativeDir
if (relativeDir != null) {
val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() }
@ -172,7 +172,6 @@ object PermissionManager {
val accessibleDirs = HashSet(getGrantedDirs(context))
accessibleDirs.addAll(context.getExternalFilesDirs(null).filterNotNull().map { it.path })
// from API 19 / Android 4.4 / KitKat, removable storage requires access permission, at the file level
// from API 21 / Android 5.0 / Lollipop, removable storage requires access permission, but directory access grant is possible
// from API 30 / Android 11 / R, any storage requires access permission
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {

View file

@ -565,7 +565,7 @@ object StorageUtils {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isMediaStoreContentUri(uri)) {
val path = uri.path
path ?: return uri
// from Android 11, accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
// from Android 11 (API 30), accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
if (path.startsWith(IMAGE_PATH_ROOT) || path.startsWith(VIDEO_PATH_ROOT)) {
// "Caller must hold ACCESS_MEDIA_LOCATION permission to access original"
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Aves</string>
<string name="app_widget_label">Foto Ram</string>
<string name="app_widget_label">Fotoram</string>
<string name="wallpaper">Bakgrund</string>
<string name="safe_mode_shortcut_short_label">Felsäkert läge</string>
<string name="videos_shortcut_short_label">Videor</string>

View file

@ -6808,6 +6808,11 @@ public class ExifInterfaceFork {
}
firstIfdOffset -= 8;
if (firstIfdOffset > 0) {
// TLAD start
if (firstIfdOffset > ATTRIBUTE_SIZE_DANGER_THRESHOLD) {
throw new IOException("dangerous IFD offset=" + firstIfdOffset);
}
// TLAD end
dataInputStream.skipFully(firstIfdOffset);
}
}

View file

@ -0,0 +1,3 @@
In v1.11.10:
- enjoy the app in Swedish
Full changelog available on GitHub

View file

@ -0,0 +1,3 @@
In v1.11.10:
- enjoy the app in Swedish
Full changelog available on GitHub

View file

@ -1,5 +1,5 @@
<i>Aves</i> kan hantera alla typer av bilder och videor, inklusive vanliga JPEG- och MP4-filer, men även mer exotiska filer som <b>flersidiga TIFF-filer, SVG-filer, gamla AVI-filer och mycket mer</b>! Den skannar din mediasamling för att identifiera <b>rörelsefoton</b>, <b>panoramor</b> (även kallade fotosfärer), <b>360° videor</b>, samnt <b>GeoTIFF</b> filer.
<i>Aves</i> kan hantera alla typer av bilder och videor, inklusive dina vanliga JPEG- och MP4-filer, men även mer exotiska filer som <b>flersidiga TIFF-filer, SVG-filer, gamla AVI-filer och mycket mer</b>! Den skannar din mediasamling för att identifiera <b>rörelsefoton</b>, <b>panoramabilder</b> (även kallade fotosfärer), <b>360° videor</b>, samt <b>GeoTIFF</b> filer.
<b>Navigering och sökhantering</b> är än viktigt del av <i>Aves</i>. Målet är att användarna på ett smidigt sätt ska kunna gå från album till foton till taggar till kartor, osv.
<b>Navigering och sökhantering</b> är än viktigt del av <i>Aves</i>. Målet är att användarna på ett smidigt sätt ska kunna gå från album till foton till etiketter till kartor, osv.
<i>Aves</i> integrerar med Android (från KitKat till Android 14, inklusive Android TV) med funktioner som <b>widgetar</b>, <b>appgenvägar</b>, <b>skärmsläckare</b> och <b>global sökhantering.</b> Den fungerar också som en <b>mediavisare och mediaväljare</b>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

View file

@ -1374,5 +1374,21 @@
"videoRepeatActionSetEnd": "Ende festlegen",
"@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "In Sammlung anzeigen",
"@chipActionShowCollection": {}
"@chipActionShowCollection": {},
"sortByDuration": "Nach Dauer",
"@sortByDuration": {},
"sortOrderShortestFirst": "Kurze zuerst",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "Lange zuerst",
"@sortOrderLongestFirst": {},
"explorerActionSelectStorageVolume": "Speicher auswählen",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Speicher auswählen",
"@selectStorageVolumeDialogTitle": {},
"setHomeCustom": "Benutzerdefiniert",
"@setHomeCustom": {},
"chipActionGoToExplorerPage": "Im Explorer anzeigen",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Explorer",
"@explorerPageTitle": {}
}

View file

@ -544,5 +544,15 @@
"authenticateToConfigureVault": "वॉल्ट को कॉन्फ़िगर करने के लिए प्रमाणीकरण करें",
"@authenticateToConfigureVault": {},
"renameAlbumDialogLabelAlreadyExistsHelper": "डायरेक्टरी पहले से मौजूद",
"@renameAlbumDialogLabelAlreadyExistsHelper": {}
"@renameAlbumDialogLabelAlreadyExistsHelper": {},
"videoRepeatActionSetEnd": "एण्ड सेट करे",
"@videoRepeatActionSetEnd": {},
"chipActionShowCountryStates": "राज्यों को दिखाएं",
"@chipActionShowCountryStates": {},
"chipActionConfigureVault": "वॉल्ट को कॉन्फ़िगर करें",
"@chipActionConfigureVault": {},
"videoActionABRepeat": "A-B दोहराव",
"@videoActionABRepeat": {},
"videoRepeatActionSetStart": "स्टार्ट सेट करे",
"@videoRepeatActionSetStart": {}
}

View file

@ -1380,5 +1380,15 @@
"selectStorageVolumeDialogTitle": "Pilih Penyimpanan",
"@selectStorageVolumeDialogTitle": {},
"setHomeCustom": "Kustom",
"@setHomeCustom": {}
"@setHomeCustom": {},
"explorerPageTitle": "Explorer",
"@explorerPageTitle": {},
"sortOrderLongestFirst": "Yang terpanjang dulu",
"@sortOrderLongestFirst": {},
"sortByDuration": "Berdasarkan durasi",
"@sortByDuration": {},
"sortOrderShortestFirst": "Yang terpendek dulu",
"@sortOrderShortestFirst": {},
"chipActionGoToExplorerPage": "Tampilkan di Explorer",
"@chipActionGoToExplorerPage": {}
}

View file

@ -1374,5 +1374,21 @@
"renameProcessorHash": "Hash",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Forza numeri arabi",
"@settingsForceWesternArabicNumeralsTile": {}
"@settingsForceWesternArabicNumeralsTile": {},
"sortByDuration": "Per durata",
"@sortByDuration": {},
"sortOrderLongestFirst": "Prima i più lunghi",
"@sortOrderLongestFirst": {},
"sortOrderShortestFirst": "Prima i più corti",
"@sortOrderShortestFirst": {},
"explorerPageTitle": "Esploratore",
"@explorerPageTitle": {},
"explorerActionSelectStorageVolume": "Seleziona il supporto",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Seleziona Supporto",
"@selectStorageVolumeDialogTitle": {},
"setHomeCustom": "Personalizzato",
"@setHomeCustom": {},
"chipActionGoToExplorerPage": "Mostra nell'Esploratore",
"@chipActionGoToExplorerPage": {}
}

View file

@ -1193,7 +1193,7 @@
"@chipActionFilterIn": {},
"filterAspectRatioPortraitLabel": "縦向き",
"@filterAspectRatioPortraitLabel": {},
"filterNoAddressLabel": "位置情報なし",
"filterNoAddressLabel": "アドレスなし",
"@filterNoAddressLabel": {},
"keepScreenOnVideoPlayback": "動画再生時",
"@keepScreenOnVideoPlayback": {},
@ -1370,5 +1370,11 @@
"cropAspectRatioOriginal": "オリジナル",
"@cropAspectRatioOriginal": {},
"stopTooltip": "停止",
"@stopTooltip": {}
"@stopTooltip": {},
"explorerPageTitle": "エクスプローラー",
"@explorerPageTitle": {},
"chipActionGoToExplorerPage": "エクスプローラーで表示",
"@chipActionGoToExplorerPage": {},
"filterLocatedLabel": "位置情報あり",
"@filterLocatedLabel": {}
}

View file

@ -1378,5 +1378,17 @@
"chipActionGoToExplorerPage": "Mostrar no Explorador",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Explorador",
"@explorerPageTitle": {}
"@explorerPageTitle": {},
"sortByDuration": "Por duração",
"@sortByDuration": {},
"sortOrderShortestFirst": "Mais curtos primeiro",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "Mais longos primeiro",
"@sortOrderLongestFirst": {},
"explorerActionSelectStorageVolume": "Selecione o armazenamento",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Selecione o Armazenamento",
"@selectStorageVolumeDialogTitle": {},
"setHomeCustom": "Personalizada",
"@setHomeCustom": {}
}

View file

@ -1532,5 +1532,21 @@
"videoRepeatActionSetEnd": "Setează sfârșitul",
"@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "Afișați în colecție",
"@chipActionShowCollection": {}
"@chipActionShowCollection": {},
"sortByDuration": "După durată",
"@sortByDuration": {},
"setHomeCustom": "Personalizat",
"@setHomeCustom": {},
"sortOrderShortestFirst": "Cel mai scurt mai întâi",
"@sortOrderShortestFirst": {},
"sortOrderLongestFirst": "Cel mai lung mai întâi",
"@sortOrderLongestFirst": {},
"explorerActionSelectStorageVolume": "Selectează spațiu de stocare",
"@explorerActionSelectStorageVolume": {},
"selectStorageVolumeDialogTitle": "Selectează spațiu de stocare",
"@selectStorageVolumeDialogTitle": {},
"chipActionGoToExplorerPage": "Afișare în Explorer",
"@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Explorer",
"@explorerPageTitle": {}
}

File diff suppressed because it is too large Load diff

View file

@ -96,6 +96,11 @@ class Contributors {
Contributor('Stephan Paternotte', 'stephan@paternottes.net'),
Contributor('Tung Anh', 'buihuutunganh2007@gmail.com'),
Contributor('Adrien N', 'adriennathaniel1999@gmail.com'),
Contributor('Shift18', 'bribable.lawyer@posteo.net'),
Contributor('Andreas Håll', 'ante_skalman@hotmail.com'),
Contributor('Scorza9999', 'oliva.scorza@gmail.com'),
Contributor('Taufan', 'taufanxxx@gmail.com'),
Contributor('Leo Aaua Felix', 'g00g7el@gmail.com'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
@ -113,8 +118,6 @@ class Contributors {
// Contributor('Subham Jena', 'subhamjena8465@gmail.com'), // Odia
// Contributor('Prasanta-Hembram', 'Prasantahembram720@gmail.com'), // Santali
// Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian
// Contributor('Shift18', 'bribable.lawyer@posteo.net'), // Swedish
// Contributor('Andreas Håll', 'ante_skalman@hotmail.com'), // Swedish
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
};
}

View file

@ -29,10 +29,10 @@ class AppInventory {
areAppNamesReadyNotifier.value = false;
}
bool isPotentialAppDir(String dir) => _potentialAppDirs.contains(dir);
bool isPotentialAppDir(String dir) => _potentialAppDirs.contains(Package.normalizePotentialDir(dir));
String? getAlbumAppPackageName(String albumPath) {
final dir = pContext.split(albumPath).last;
final dir = Package.normalizePotentialDir(pContext.split(albumPath).last);
final package = _launcherPackages.firstWhereOrNull((v) => v.potentialDirs.contains(dir));
return package?.packageName;
}
@ -71,7 +71,11 @@ class Package {
currentLabel,
englishLabel,
...ownedDirs,
].whereNotNull().toSet();
].whereNotNull().map(normalizePotentialDir).toSet();
static String normalizePotentialDir(String dir) {
return dir.replaceAll('_', ' ').trim().toLowerCase();
}
@override
String toString() => '$runtimeType#${shortHash(this)}{packageName=$packageName, categoryLauncher=$categoryLauncher, isSystem=$isSystem, currentLabel=$currentLabel, englishLabel=$englishLabel, ownedDirs=$ownedDirs}';

View file

@ -32,7 +32,7 @@ class Covers {
Covers._private();
Future<void> init() async {
_rows = await metadataDb.loadAllCovers();
_rows = await localMediaDb.loadAllCovers();
}
int get count => _rows.length;
@ -59,7 +59,7 @@ class Covers {
final oldRows = _rows.where((row) => row.filter == filter).toSet();
_rows.removeAll(oldRows);
await metadataDb.removeCovers({filter});
await localMediaDb.removeCovers({filter});
final oldRow = oldRows.firstOrNull;
final oldEntry = oldRow?.entryId;
@ -74,7 +74,7 @@ class Covers {
color: color,
);
_rows.add(row);
await metadataDb.addCovers({row});
await localMediaDb.addCovers({row});
}
if (oldEntry != entryId) _entryChangeStreamController.add({filter});
@ -103,7 +103,7 @@ class Covers {
}
Future<void> clear() async {
await metadataDb.clearCovers();
await localMediaDb.clearCovers();
_rows.clear();
_entryChangeStreamController.add(null);

View file

@ -8,7 +8,7 @@ import 'package:aves/model/metadata/trash.dart';
import 'package:aves/model/vaults/details.dart';
import 'package:aves/model/viewer/video_playback.dart';
abstract class MetadataDb {
abstract class LocalMediaDb {
int get nextId;
Future<void> init();
@ -27,12 +27,14 @@ abstract class MetadataDb {
Future<Set<AvesEntry>> loadEntriesById(Set<int> ids);
Future<void> saveEntries(Set<AvesEntry> entries);
Future<void> insertEntries(Set<AvesEntry> entries);
Future<void> updateEntry(int id, AvesEntry entry);
Future<Set<AvesEntry>> searchLiveEntries(String query, {int? limit});
Future<Set<AvesEntry>> searchLiveDuplicates(int origin, Set<AvesEntry>? entries);
// date taken
Future<void> clearDates();

View file

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:aves/model/covers.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db_metadata_sqflite_upgrade.dart';
import 'package:aves/model/db/db.dart';
import 'package:aves/model/db/db_sqflite_upgrade.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/filters.dart';
@ -16,7 +16,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:sqflite/sqflite.dart';
class SqfliteMetadataDb implements MetadataDb {
class SqfliteLocalMediaDb implements LocalMediaDb {
late Database _db;
Future<String> get path async => pContext.join(await getDatabasesPath(), 'metadata.db');
@ -108,11 +108,11 @@ class SqfliteMetadataDb implements MetadataDb {
', resumeTimeMillis INTEGER'
')');
},
onUpgrade: MetadataDbUpgrader.upgradeDb,
onUpgrade: LocalMediaDbUpgrader.upgradeDb,
version: 11,
);
final maxIdRows = await _db.rawQuery('SELECT max(id) AS maxId FROM $entryTable');
final maxIdRows = await _db.rawQuery('SELECT MAX(id) AS maxId FROM $entryTable');
_lastId = (maxIdRows.firstOrNull?['maxId'] as int?) ?? 0;
}
@ -209,7 +209,7 @@ class SqfliteMetadataDb implements MetadataDb {
Future<Set<AvesEntry>> loadEntriesById(Set<int> ids) => _getByIds(ids, entryTable, AvesEntry.fromMap);
@override
Future<void> saveEntries(Set<AvesEntry> entries) async {
Future<void> insertEntries(Set<AvesEntry> entries) async {
if (entries.isEmpty) return;
final stopwatch = Stopwatch()..start();
final batch = _db.batch();
@ -246,6 +246,28 @@ class SqfliteMetadataDb implements MetadataDb {
return rows.map(AvesEntry.fromMap).toSet();
}
@override
Future<Set<AvesEntry>> searchLiveDuplicates(int origin, Set<AvesEntry>? entries) async {
String where = 'origin = ? AND trashed = ?';
if (entries != null) {
where += ' AND contentId IN (${entries.map((v) => v.contentId).join(',')})';
}
final rows = await _db.rawQuery(
'SELECT *, MAX(id) AS id'
' FROM $entryTable'
' WHERE $where'
' GROUP BY contentId'
' HAVING COUNT(id) > 1',
[origin, 0],
);
final duplicates = rows.map(AvesEntry.fromMap).toSet();
if (duplicates.isNotEmpty) {
debugPrint('Found duplicates=$duplicates');
}
// return most recent duplicate for each duplicated content ID
return duplicates;
}
// date taken
@override

View file

@ -1,18 +1,18 @@
import 'package:aves/model/db/db_metadata_sqflite.dart';
import 'package:aves/model/db/db_sqflite.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:sqflite/sqflite.dart';
class MetadataDbUpgrader {
static const entryTable = SqfliteMetadataDb.entryTable;
static const dateTakenTable = SqfliteMetadataDb.dateTakenTable;
static const metadataTable = SqfliteMetadataDb.metadataTable;
static const addressTable = SqfliteMetadataDb.addressTable;
static const favouriteTable = SqfliteMetadataDb.favouriteTable;
static const coverTable = SqfliteMetadataDb.coverTable;
static const vaultTable = SqfliteMetadataDb.vaultTable;
static const trashTable = SqfliteMetadataDb.trashTable;
static const videoPlaybackTable = SqfliteMetadataDb.videoPlaybackTable;
class LocalMediaDbUpgrader {
static const entryTable = SqfliteLocalMediaDb.entryTable;
static const dateTakenTable = SqfliteLocalMediaDb.dateTakenTable;
static const metadataTable = SqfliteLocalMediaDb.metadataTable;
static const addressTable = SqfliteLocalMediaDb.addressTable;
static const favouriteTable = SqfliteLocalMediaDb.favouriteTable;
static const coverTable = SqfliteLocalMediaDb.coverTable;
static const vaultTable = SqfliteLocalMediaDb.vaultTable;
static const trashTable = SqfliteLocalMediaDb.trashTable;
static const videoPlaybackTable = SqfliteLocalMediaDb.videoPlaybackTable;
// warning: "ALTER TABLE ... RENAME COLUMN ..." is not supported
// on SQLite <3.25.0, bundled on older Android devices

View file

@ -432,8 +432,8 @@ class AvesEntry with AvesEntryBase {
if (isFlipped is bool) this.isFlipped = isFlipped;
if (persist) {
await metadataDb.saveEntries({this});
if (catalogMetadata != null) await metadataDb.saveCatalogMetadata({catalogMetadata!});
await localMediaDb.updateEntry(id, this);
if (catalogMetadata != null) await localMediaDb.saveCatalogMetadata({catalogMetadata!});
}
await _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped);
@ -451,7 +451,7 @@ class AvesEntry with AvesEntryBase {
_tags = null;
if (persist) {
await metadataDb.removeIds({id}, dataTypes: dataTypes);
await localMediaDb.removeIds({id}, dataTypes: dataTypes);
}
final updatedEntry = await mediaFetchService.getEntry(uri, mimeType);

View file

@ -15,7 +15,7 @@ class Favourites with ChangeNotifier {
Favourites._private();
Future<void> init() async {
_rows = await metadataDb.loadAllFavourites();
_rows = await localMediaDb.loadAllFavourites();
}
int get count => _rows.length;
@ -29,7 +29,7 @@ class Favourites with ChangeNotifier {
Future<void> add(Set<AvesEntry> entries) async {
final newRows = entries.map(_entryToRow).toSet();
await metadataDb.addFavourites(newRows);
await localMediaDb.addFavourites(newRows);
_rows.addAll(newRows);
notifyListeners();
@ -40,14 +40,14 @@ class Favourites with ChangeNotifier {
Future<void> removeIds(Set<int> entryIds) async {
final removedRows = _rows.where((row) => entryIds.contains(row.entryId)).toSet();
await metadataDb.removeFavourites(removedRows);
await localMediaDb.removeFavourites(removedRows);
removedRows.forEach(_rows.remove);
notifyListeners();
}
Future<void> clear() async {
await metadataDb.clearFavourites();
await localMediaDb.clearFavourites();
_rows.clear();
notifyListeners();

View file

@ -136,7 +136,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
late Map<int?, int?> _savedDates;
Future<void> loadDates() async {
_savedDates = Map.unmodifiable(await metadataDb.loadDates());
_savedDates = Map.unmodifiable(await localMediaDb.loadDates());
}
Set<CollectionFilter> _getAppHiddenFilters() => {
@ -217,7 +217,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
final ids = entries.map((entry) => entry.id).toSet();
await favourites.removeIds(ids);
await covers.removeIds(ids);
await metadataDb.removeIds(ids);
await localMediaDb.removeIds(ids);
ids.forEach((id) => _entryById.remove);
_rawEntries.removeAll(entries);
@ -278,10 +278,10 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
if (persist) {
await covers.moveEntry(entry);
final id = entry.id;
await metadataDb.updateEntry(id, entry);
await metadataDb.updateCatalogMetadata(id, entry.catalogMetadata);
await metadataDb.updateAddress(id, entry.addressDetails);
await metadataDb.updateTrash(id, entry.trashDetails);
await localMediaDb.updateEntry(id, entry);
await localMediaDb.updateCatalogMetadata(id, entry.catalogMetadata);
await localMediaDb.updateAddress(id, entry.addressDetails);
await localMediaDb.updateTrash(id, entry.trashDetails);
}
}
@ -352,7 +352,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
if (sourceEntry != null) {
fromAlbums.add(sourceEntry.directory);
movedEntries.add(sourceEntry.copyWith(
id: metadataDb.nextId,
id: localMediaDb.nextId,
uri: newFields['uri'] as String?,
path: newFields['path'] as String?,
contentId: newFields['contentId'] as int?,
@ -366,9 +366,9 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
debugPrint('failed to find source entry with uri=$sourceUri');
}
});
await metadataDb.saveEntries(movedEntries);
await metadataDb.saveCatalogMetadata(movedEntries.map((entry) => entry.catalogMetadata).whereNotNull().toSet());
await metadataDb.saveAddresses(movedEntries.map((entry) => entry.addressDetails).whereNotNull().toSet());
await localMediaDb.insertEntries(movedEntries);
await localMediaDb.saveCatalogMetadata(movedEntries.map((entry) => entry.catalogMetadata).whereNotNull().toSet());
await localMediaDb.saveAddresses(movedEntries.map((entry) => entry.addressDetails).whereNotNull().toSet());
} else {
await Future.forEach<MoveOpEvent>(movedOps, (movedOp) async {
final newFields = movedOp.newFields;
@ -455,7 +455,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
await deviceService.requestGarbageCollection();
await Future.forEach(entries, (entry) async {
await entry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist);
await metadataDb.updateCatalogMetadata(entry.id, entry.catalogMetadata);
await localMediaDb.updateCatalogMetadata(entry.id, entry.catalogMetadata);
});
onCatalogMetadataChanged();
}
@ -463,7 +463,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
if (dataTypes.contains(EntryDataType.address)) {
await Future.forEach(entries, (entry) async {
await entry.locate(background: background, force: dataTypes.contains(EntryDataType.address), geocoderLocale: settings.appliedLocale);
await metadataDb.updateAddress(entry.id, entry.addressDetails);
await localMediaDb.updateAddress(entry.id, entry.addressDetails);
});
onAddressMetadataChanged();
}
@ -497,7 +497,6 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
}
}
if (startAnalysisService) {
// TODO TLAD [tiramisu] explain foreground service and request POST_NOTIFICATIONS permission
await AnalysisService.startService(
force: force,
entryIds: entries?.map((entry) => entry.id).toList(),

View file

@ -24,7 +24,7 @@ mixin LocationMixin on CountryMixin, StateMixin {
List<String> sortedPlaces = List.unmodifiable([]);
Future<void> loadAddresses({Set<int>? ids}) async {
final saved = await (ids != null ? metadataDb.loadAddressesById(ids) : metadataDb.loadAddresses());
final saved = await (ids != null ? localMediaDb.loadAddressesById(ids) : localMediaDb.loadAddresses());
final idMap = entryById;
saved.forEach((metadata) => idMap[metadata.id]?.addressDetails = metadata);
invalidateEntries();
@ -37,7 +37,7 @@ mixin LocationMixin on CountryMixin, StateMixin {
final unlocatedIds = candidateEntries.where((entry) => !entry.hasGps).map((entry) => entry.id).toSet();
if (unlocatedIds.isNotEmpty) {
await metadataDb.removeIds(unlocatedIds, dataTypes: {EntryDataType.address});
await localMediaDb.removeIds(unlocatedIds, dataTypes: {EntryDataType.address});
onAddressMetadataChanged();
}
}
@ -71,7 +71,7 @@ mixin LocationMixin on CountryMixin, StateMixin {
setProgress(done: ++progressDone, total: progressTotal);
});
if (newAddresses.isNotEmpty) {
await metadataDb.saveAddresses(Set.unmodifiable(newAddresses));
await localMediaDb.saveAddresses(Set.unmodifiable(newAddresses));
onAddressMetadataChanged();
}
}
@ -129,7 +129,7 @@ mixin LocationMixin on CountryMixin, StateMixin {
if (entry.hasFineAddress) {
newAddresses.add(entry.addressDetails!);
if (newAddresses.length >= commitCountThreshold) {
await metadataDb.saveAddresses(Set.unmodifiable(newAddresses));
await localMediaDb.saveAddresses(Set.unmodifiable(newAddresses));
onAddressMetadataChanged();
newAddresses.clear();
}
@ -141,7 +141,7 @@ mixin LocationMixin on CountryMixin, StateMixin {
setProgress(done: ++progressDone, total: progressTotal);
}
if (newAddresses.isNotEmpty) {
await metadataDb.saveAddresses(Set.unmodifiable(newAddresses));
await localMediaDb.saveAddresses(Set.unmodifiable(newAddresses));
onAddressMetadataChanged();
}
}

View file

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'package:aves/model/covers.dart';
import 'package:aves/model/entry/entry.dart';
@ -57,7 +56,7 @@ class MediaStoreSource extends CollectionSource {
Future<void> _loadEssentials() async {
final stopwatch = Stopwatch()..start();
state = SourceState.loading;
await metadataDb.init();
await localMediaDb.init();
await vaults.init();
await favourites.init();
await covers.init();
@ -67,8 +66,8 @@ class MediaStoreSource extends CollectionSource {
if (currentTimeZoneOffset != catalogTimeZoneOffset) {
// clear catalog metadata to get correct date/times when moving to a different time zone
debugPrint('$runtimeType clear catalog metadata to get correct date/times');
await metadataDb.clearDates();
await metadataDb.clearCatalogMetadata();
await localMediaDb.clearDates();
await localMediaDb.clearCatalogMetadata();
settings.catalogTimeZoneRawOffsetMillis = currentTimeZoneOffset;
}
}
@ -92,13 +91,13 @@ class MediaStoreSource extends CollectionSource {
final topIds = settings.topEntryIds?.toSet();
if (topIds != null) {
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load ${topIds.length} top entries');
topEntries.addAll(await metadataDb.loadEntriesById(topIds));
topEntries.addAll(await localMediaDb.loadEntriesById(topIds));
addEntries(topEntries);
}
}
debugPrint('$runtimeType refresh ${stopwatch.elapsed} fetch known entries');
final knownEntries = await metadataDb.loadEntries(origin: EntryOrigins.mediaStoreContent, directory: directory);
final knownEntries = await localMediaDb.loadEntries(origin: EntryOrigins.mediaStoreContent, directory: directory);
final knownLiveEntries = knownEntries.where((entry) => !entry.trashed).toSet();
debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete entries');
@ -146,7 +145,7 @@ class MediaStoreSource extends CollectionSource {
// clean up obsolete entries
if (removedEntries.isNotEmpty) {
debugPrint('$runtimeType refresh ${stopwatch.elapsed} remove obsolete entries');
await metadataDb.removeIds(removedEntries.map((entry) => entry.id).toSet());
await localMediaDb.removeIds(removedEntries.map((entry) => entry.id).toSet());
}
// verify paths because some apps move files without updating their `last modified date`
@ -159,46 +158,44 @@ class MediaStoreSource extends CollectionSource {
});
// items to add to the collection
final pendingNewEntries = <AvesEntry>{};
final newEntries = <AvesEntry>{};
// recover untracked trash items
debugPrint('$runtimeType refresh ${stopwatch.elapsed} recover untracked entries');
if (directory == null) {
pendingNewEntries.addAll(await recoverUntrackedTrashItems());
newEntries.addAll(await recoverUntrackedTrashItems());
}
// fetch new & modified entries
debugPrint('$runtimeType refresh ${stopwatch.elapsed} fetch new entries');
// refresh after the first 10 entries, then after 100 more, then every 1000 entries
var refreshCount = 10;
const refreshCountMax = 1000;
final allNewEntries = <AvesEntry>{};
void addPendingEntries() {
allNewEntries.addAll(pendingNewEntries);
addEntries(pendingNewEntries);
pendingNewEntries.clear();
}
mediaStoreService.getEntries(_safeMode, knownDateByContentId, directory: directory).listen(
(entry) {
// when discovering modified entry with known content ID,
// reuse known entry ID to overwrite it while preserving favourites, etc.
final contentId = entry.contentId;
final existingEntry = knownContentIds.contains(contentId) ? knownLiveEntries.firstWhereOrNull((entry) => entry.contentId == contentId) : null;
entry.id = existingEntry?.id ?? metadataDb.nextId;
entry.id = existingEntry?.id ?? localMediaDb.nextId;
pendingNewEntries.add(entry);
if (pendingNewEntries.length >= refreshCount) {
refreshCount = min(refreshCount * 10, refreshCountMax);
addPendingEntries();
}
newEntries.add(entry);
},
onDone: () async {
addPendingEntries();
if (allNewEntries.isNotEmpty) {
if (newEntries.isNotEmpty) {
debugPrint('$runtimeType refresh ${stopwatch.elapsed} save new entries');
await metadataDb.saveEntries(allNewEntries);
await localMediaDb.insertEntries(newEntries);
// TODO TLAD find duplication cause
final duplicates = await localMediaDb.searchLiveDuplicates(EntryOrigins.mediaStoreContent, newEntries);
if (duplicates.isNotEmpty) {
unawaited(reportService.recordError(Exception('Loading entries yielded duplicates=${duplicates.join(', ')}'), StackTrace.current));
// post-error cleanup
await localMediaDb.removeIds(duplicates.map((v) => v.id).toSet());
for (final duplicate in duplicates) {
final duplicateId = duplicate.id;
newEntries.removeWhere((v) => duplicateId == v.id);
}
}
addEntries(newEntries);
// new entries include existing entries with obsolete paths
// so directories may be added, but also removed or simply have their content summary changed
@ -224,7 +221,7 @@ class MediaStoreSource extends CollectionSource {
notifyAlbumsChanged();
debugPrint('$runtimeType refresh ${stopwatch.elapsed} done');
unawaited(reportService.log('Source refresh complete in ${stopwatch.elapsed.inSeconds}s for ${knownEntries.length} known, ${allNewEntries.length} new, ${removedEntries.length} removed'));
unawaited(reportService.log('Source refresh complete in ${stopwatch.elapsed.inSeconds}s for ${knownEntries.length} known, ${newEntries.length} new, ${removedEntries.length} removed'));
},
onError: (error) => debugPrint('$runtimeType stream error=$error'),
);
@ -242,7 +239,7 @@ class MediaStoreSource extends CollectionSource {
state = SourceState.loading;
debugPrint('$runtimeType refreshUris ${changedUris.length} uris');
final uriByContentId = Map.fromEntries(changedUris.map((uri) {
final changedUriByContentId = Map.fromEntries(changedUris.map((uri) {
final pathSegments = Uri.parse(uri).pathSegments;
// e.g. URI `content://media/` has no path segment
if (pathSegments.isEmpty) return null;
@ -253,16 +250,16 @@ class MediaStoreSource extends CollectionSource {
}).whereNotNull());
// clean up obsolete entries
final obsoleteContentIds = (await mediaStoreService.checkObsoleteContentIds(uriByContentId.keys.toList())).toSet();
final obsoleteUris = obsoleteContentIds.map((contentId) => uriByContentId[contentId]).whereNotNull().toSet();
final obsoleteContentIds = (await mediaStoreService.checkObsoleteContentIds(changedUriByContentId.keys.toList())).toSet();
final obsoleteUris = obsoleteContentIds.map((contentId) => changedUriByContentId[contentId]).whereNotNull().toSet();
await removeEntries(obsoleteUris, includeTrash: false);
obsoleteContentIds.forEach(uriByContentId.remove);
obsoleteContentIds.forEach(changedUriByContentId.remove);
// fetch new entries
final tempUris = <String>{};
final newEntries = <AvesEntry>{}, entriesToRefresh = <AvesEntry>{};
final existingDirectories = <String>{};
for (final kv in uriByContentId.entries) {
for (final kv in changedUriByContentId.entries) {
final contentId = kv.key;
final uri = kv.value;
final sourceEntry = await mediaFetchService.getEntry(uri, null);
@ -276,7 +273,7 @@ class MediaStoreSource extends CollectionSource {
if (existingEntry != null) {
entriesToRefresh.add(existingEntry);
} else {
sourceEntry.id = metadataDb.nextId;
sourceEntry.id = localMediaDb.nextId;
newEntries.add(sourceEntry);
}
final existingDirectory = existingEntry?.directory;
@ -303,8 +300,22 @@ class MediaStoreSource extends CollectionSource {
state = SourceState.ready;
if (newEntries.isNotEmpty) {
await localMediaDb.insertEntries(newEntries);
// TODO TLAD find duplication cause
final duplicates = await localMediaDb.searchLiveDuplicates(EntryOrigins.mediaStoreContent, newEntries);
if (duplicates.isNotEmpty) {
unawaited(reportService.recordError(Exception('Refreshing entries yielded duplicates=${duplicates.join(', ')}'), StackTrace.current));
// post-error cleanup
await localMediaDb.removeIds(duplicates.map((v) => v.id).toSet());
for (final duplicate in duplicates) {
final duplicateId = duplicate.id;
newEntries.removeWhere((v) => duplicateId == v.id);
tempUris.add(duplicate.uri);
}
}
addEntries(newEntries);
await metadataDb.saveEntries(newEntries);
await analyze(analysisController, entries: newEntries);
}
@ -346,7 +357,7 @@ class MediaStoreSource extends CollectionSource {
// vault
Future<void> _loadVaultEntries(String? directory) async {
addEntries(await metadataDb.loadEntries(origin: EntryOrigins.vault, directory: directory));
addEntries(await localMediaDb.loadEntries(origin: EntryOrigins.vault, directory: directory));
}
Future<void> _refreshVaultEntries({
@ -367,7 +378,7 @@ class MediaStoreSource extends CollectionSource {
final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
if (sourceEntry != null) {
newEntries.add(sourceEntry.copyWith(
id: metadataDb.nextId,
id: localMediaDb.nextId,
origin: EntryOrigins.vault,
));
}

View file

@ -17,7 +17,7 @@ mixin TagMixin on SourceBase {
List<String> sortedTags = List.unmodifiable([]);
Future<void> loadCatalogMetadata({Set<int>? ids}) async {
final saved = await (ids != null ? metadataDb.loadCatalogMetadataById(ids) : metadataDb.loadCatalogMetadata());
final saved = await (ids != null ? localMediaDb.loadCatalogMetadataById(ids) : localMediaDb.loadCatalogMetadata());
final idMap = entryById;
saved.forEach((metadata) => idMap[metadata.id]?.catalogMetadata = metadata);
invalidateEntries();
@ -48,7 +48,7 @@ mixin TagMixin on SourceBase {
if (entry.isCatalogued) {
newMetadata.add(entry.catalogMetadata!);
if (newMetadata.length >= commitCountThreshold) {
await metadataDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
await localMediaDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
onCatalogMetadataChanged();
newMetadata.clear();
}
@ -59,7 +59,7 @@ mixin TagMixin on SourceBase {
}
setProgress(done: ++progressDone, total: progressTotal);
}
await metadataDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
await localMediaDb.saveCatalogMetadata(Set.unmodifiable(newMetadata));
onCatalogMetadataChanged();
}

View file

@ -14,7 +14,7 @@ mixin TrashMixin on SourceBase {
static const Duration binKeepDuration = Duration(days: 30);
Future<void> loadTrashDetails() async {
final saved = await metadataDb.loadAllTrashDetails();
final saved = await localMediaDb.loadAllTrashDetails();
final idMap = entryById;
saved.forEach((details) => idMap[details.id]?.trashDetails = details);
}
@ -63,13 +63,13 @@ mixin TrashMixin on SourceBase {
entry.trashed = true;
entry.trashDetails = _buildTrashDetails(id);
// persist
await metadataDb.updateEntry(id, entry);
await metadataDb.updateTrash(id, entry.trashDetails);
await localMediaDb.updateEntry(id, entry);
await localMediaDb.updateTrash(id, entry.trashDetails);
} else {
// there is no matching entry
final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
if (sourceEntry != null) {
final id = metadataDb.nextId;
final id = localMediaDb.nextId;
sourceEntry.id = id;
sourceEntry.path = pContext.join(recoveryPath, pContext.basename(untrackedPath));
sourceEntry.trashed = true;

View file

@ -23,7 +23,7 @@ class Vaults extends ChangeNotifier {
Vaults._private();
Future<void> init() async {
_rows = await metadataDb.loadAllVaults();
_rows = await localMediaDb.loadAllVaults();
_vaultDirPaths = null;
final screenStateStream = Platform.isAndroid ? AvesScreenState().screenStateStream : null;
if (screenStateStream != null) {
@ -44,7 +44,7 @@ class Vaults extends ChangeNotifier {
VaultDetails? detailsForPath(String dirPath) => _rows.firstWhereOrNull((v) => v.path == dirPath);
Future<void> create(VaultDetails details) async {
await metadataDb.addVaults({details});
await localMediaDb.addVaults({details});
_rows.add(details);
_vaultDirPaths = null;
@ -56,7 +56,7 @@ class Vaults extends ChangeNotifier {
final details = dirPaths.map(detailsForPath).whereNotNull().toSet();
if (details.isEmpty) return;
await metadataDb.removeVaults(details);
await localMediaDb.removeVaults(details);
await Future.forEach(details, (v) => securityService.writeValue(v.passKey, null));
@ -74,7 +74,7 @@ class Vaults extends ChangeNotifier {
if (newName == null) return;
final newDetails = oldDetails.copyWith(name: newName);
await metadataDb.updateVault(oldDetails.name, newDetails);
await localMediaDb.updateVault(oldDetails.name, newDetails);
final pass = await securityService.readValue(oldDetails.passKey);
if (pass != null) {
@ -96,7 +96,7 @@ class Vaults extends ChangeNotifier {
final oldDetails = detailsForPath(newDetails.path);
if (oldDetails == null) return;
await metadataDb.updateVault(newDetails.name, newDetails);
await localMediaDb.updateVault(newDetails.name, newDetails);
_rows
..remove(oldDetails)
@ -104,7 +104,7 @@ class Vaults extends ChangeNotifier {
}
Future<void> clear() async {
await metadataDb.clearVaults();
await localMediaDb.clearVaults();
_rows.clear();
_vaultDirPaths = null;
}
@ -146,7 +146,7 @@ class Vaults extends ChangeNotifier {
final newEntries = await recoverUntrackedItems(source, dirPath);
if (newEntries.isNotEmpty) {
source.addEntries(newEntries);
await metadataDb.saveEntries(newEntries);
await localMediaDb.insertEntries(newEntries);
unawaited(source.analyze(null, entries: newEntries));
}
@ -168,7 +168,7 @@ class Vaults extends ChangeNotifier {
final uri = Uri.file(untrackedPath).toString();
final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
if (sourceEntry != null) {
sourceEntry.id = metadataDb.nextId;
sourceEntry.id = localMediaDb.nextId;
sourceEntry.origin = EntryOrigins.vault;
newEntries.add(sourceEntry);
} else {

View file

@ -13,6 +13,7 @@ import 'package:aves_model/aves_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:permission_handler/permission_handler.dart';
class AnalysisService {
static const _platform = MethodChannel('deckers.thibault/aves/analysis');
@ -29,6 +30,10 @@ class AnalysisService {
}
static Future<void> startService({required bool force, List<int>? entryIds}) async {
// from Android 13 (API 33), notifications are off by default,
// so the user needs to grant the permission to see the service notification
unawaited(Permission.notification.request());
await reportService.log('Start analysis service${entryIds != null ? ' for ${entryIds.length} items' : ''}');
try {
await _platform.invokeMethod('startAnalysis', <String, dynamic>{
@ -48,7 +53,7 @@ Future<void> _init() async {
WidgetsFlutterBinding.ensureInitialized();
initPlatformServices();
await androidFileUtils.init();
await metadataDb.init();
await localMediaDb.init();
await device.init();
await mobileServices.init();
await settings.init(monitorPlatformSettings: false);

View file

@ -43,7 +43,7 @@ class PlatformAppService implements AppService {
'com.sony.playmemories.mobile': {'Imaging Edge Mobile'},
'nekox.messenger': {'NekoX'},
'org.telegram.messenger': {'Telegram Images', 'Telegram Video'},
'com.whatsapp': {'Whatsapp', 'WhatsApp Animated Gifs', 'WhatsApp Documents', 'WhatsApp Images', 'WhatsApp Video'}
'com.whatsapp': {'WhatsApp Animated Gifs', 'WhatsApp Documents', 'WhatsApp Images', 'WhatsApp Video'}
};
@override

View file

@ -1,6 +1,6 @@
import 'package:aves/model/availability.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db_metadata_sqflite.dart';
import 'package:aves/model/db/db.dart';
import 'package:aves/model/db/db_sqflite.dart';
import 'package:aves/model/settings/store_shared_pref.dart';
import 'package:aves/services/app_service.dart';
import 'package:aves/services/device_service.dart';
@ -32,7 +32,7 @@ final SettingsStore settingsStore = SharedPrefSettingsStore();
final p.Context pContext = getIt<p.Context>();
final AvesAvailability availability = getIt<AvesAvailability>();
final MetadataDb metadataDb = getIt<MetadataDb>();
final LocalMediaDb localMediaDb = getIt<LocalMediaDb>();
final AvesVideoControllerFactory videoControllerFactory = getIt<AvesVideoControllerFactory>();
final AvesVideoMetadataFetcher videoMetadataFetcher = getIt<AvesVideoMetadataFetcher>();
@ -54,7 +54,7 @@ final WindowService windowService = getIt<WindowService>();
void initPlatformServices() {
getIt.registerLazySingleton<p.Context>(p.Context.new);
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
getIt.registerLazySingleton<LocalMediaDb>(SqfliteLocalMediaDb.new);
getIt.registerLazySingleton<AvesVideoControllerFactory>(MpvVideoControllerFactory.new);
getIt.registerLazySingleton<AvesVideoMetadataFetcher>(FfmpegVideoMetadataFetcher.new);

View file

@ -29,7 +29,7 @@ Future<void> _init() async {
// service initialization for path context, database
initPlatformServices();
await metadataDb.init();
await localMediaDb.init();
// `intl` initialization for date formatting
await initializeDateFormatting();
@ -55,8 +55,8 @@ Future<List<Map<String, String?>>> _getSuggestions(dynamic args) async {
debugPrint('getSuggestions query=$query, locale=$locale use24hour=$use24hour');
if (query is String && locale is String) {
final entries = (await metadataDb.searchLiveEntries(query, limit: 9)).toList();
final catalogMetadata = await metadataDb.loadCatalogMetadataById(entries.map((entry) => entry.id).toSet());
final entries = (await localMediaDb.searchLiveEntries(query, limit: 9)).toList();
final catalogMetadata = await localMediaDb.loadCatalogMetadataById(entries.map((entry) => entry.id).toSet());
catalogMetadata.forEach((metadata) => entries.firstWhereOrNull((entry) => entry.id == metadata.id)?.catalogMetadata = metadata);
entries.sort(AvesEntrySort.compareByDate);

View file

@ -71,7 +71,6 @@ class AvesApp extends StatefulWidget {
'or', // Odia
'sat', // Santali
'sl', // Slovenian
'sv', // Swedish
'th', // Thai
}.map(Locale.new).toSet();
static final List<Locale> supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList();
@ -648,9 +647,11 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
Future<void> _onAnalysisCompletion() async {
debugPrint('Analysis completed');
await _mediaStoreSource.loadCatalogMetadata();
await _mediaStoreSource.loadAddresses();
_mediaStoreSource.updateDerivedFilters();
if (_mediaStoreSource.initState != SourceInitializationState.none) {
await _mediaStoreSource.loadCatalogMetadata();
await _mediaStoreSource.loadAddresses();
_mediaStoreSource.updateDerivedFilters();
}
}
void _onError(String? error) => reportService.recordError(error, null);

View file

@ -20,6 +20,7 @@ import 'package:aves/widgets/collection/draggable_thumb_label.dart';
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
import 'package:aves/widgets/collection/grid/section_layout.dart';
import 'package:aves/widgets/collection/grid/tile.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/basic/draggable_scrollbar/scrollbar.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/behaviour/routes.dart';
@ -587,7 +588,13 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
valueListenable: collection.source.stateNotifier,
builder: (context, sourceState, child) {
if (sourceState == SourceState.loading) {
return const SizedBox();
return EmptyContent(
text: context.l10n.sourceStateLoading,
bottom: const Padding(
padding: EdgeInsets.only(top: 16),
child: ReportProgressIndicator(),
),
);
}
return FutureBuilder<bool>(

View file

@ -166,6 +166,9 @@ class ReportOverlay<T> extends StatefulWidget {
final VoidCallback? onCancel;
final void Function(Set<T> processed) onDone;
static const double diameter = 160.0;
static const double strokeWidth = 8.0;
const ReportOverlay({
super.key,
required this.opStream,
@ -186,8 +189,6 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
Stream<T> get opStream => widget.opStream;
static const double fontSize = 18.0;
static const double diameter = 160.0;
static const double strokeWidth = 8.0;
@override
void initState() {
@ -222,6 +223,8 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
@override
Widget build(BuildContext context) {
const diameter = ReportOverlay.diameter;
const strokeWidth = ReportOverlay.strokeWidth;
final percentFormatter = NumberFormat.percentPattern(context.locale);
final theme = Theme.of(context);
@ -249,16 +252,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
shape: BoxShape.circle,
),
),
if (animate)
Container(
width: diameter,
height: diameter,
padding: const EdgeInsets.all(strokeWidth / 2),
child: CircularProgressIndicator(
color: progressColor.withOpacity(.1),
strokeWidth: strokeWidth,
),
),
if (animate) const ReportProgressIndicator(opacity: .1),
CircularPercentIndicator(
percent: percent,
lineWidth: strokeWidth,
@ -301,6 +295,32 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
}
}
class ReportProgressIndicator extends StatelessWidget {
final double opacity;
const ReportProgressIndicator({
super.key,
this.opacity = 1,
});
@override
Widget build(BuildContext context) {
const diameter = ReportOverlay.diameter;
const strokeWidth = ReportOverlay.strokeWidth;
final progressColor = Theme.of(context).colorScheme.primary;
return Container(
width: diameter,
height: diameter,
padding: const EdgeInsets.all(strokeWidth / 2),
child: CircularProgressIndicator(
color: progressColor.withOpacity(opacity),
strokeWidth: strokeWidth,
),
);
}
}
class _FeedbackMessage extends StatefulWidget {
final FeedbackType type;
final String message;

View file

@ -70,7 +70,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => metadataDb.reset().then((_) => _reload()),
onPressed: () => localMediaDb.reset().then((_) => _reload()),
child: const Text('Reset'),
),
],
@ -93,7 +93,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => metadataDb.clearEntries().then((_) => _reload()),
onPressed: () => localMediaDb.clearEntries().then((_) => _reload()),
child: const Text('Clear'),
),
],
@ -114,7 +114,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => metadataDb.clearDates().then((_) => _reload()),
onPressed: () => localMediaDb.clearDates().then((_) => _reload()),
child: const Text('Clear'),
),
],
@ -135,7 +135,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => metadataDb.clearCatalogMetadata().then((_) => _reload()),
onPressed: () => localMediaDb.clearCatalogMetadata().then((_) => _reload()),
child: const Text('Clear'),
),
],
@ -156,7 +156,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => metadataDb.clearAddresses().then((_) => _reload()),
onPressed: () => localMediaDb.clearAddresses().then((_) => _reload()),
child: const Text('Clear'),
),
],
@ -177,7 +177,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => metadataDb.clearTrashDetails().then((_) => _reload()),
onPressed: () => localMediaDb.clearTrashDetails().then((_) => _reload()),
child: const Text('Clear'),
),
],
@ -261,7 +261,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => metadataDb.clearVideoPlayback().then((_) => _reload()),
onPressed: () => localMediaDb.clearVideoPlayback().then((_) => _reload()),
child: const Text('Clear'),
),
],
@ -281,16 +281,16 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
}
void _startDbReport() {
_dbFileSizeLoader = metadataDb.dbFileSize();
_dbEntryLoader = metadataDb.loadEntries();
_dbDateLoader = metadataDb.loadDates();
_dbMetadataLoader = metadataDb.loadCatalogMetadata();
_dbAddressLoader = metadataDb.loadAddresses();
_dbTrashLoader = metadataDb.loadAllTrashDetails();
_dbVaultsLoader = metadataDb.loadAllVaults();
_dbFavouritesLoader = metadataDb.loadAllFavourites();
_dbCoversLoader = metadataDb.loadAllCovers();
_dbVideoPlaybackLoader = metadataDb.loadAllVideoPlayback();
_dbFileSizeLoader = localMediaDb.dbFileSize();
_dbEntryLoader = localMediaDb.loadEntries();
_dbDateLoader = localMediaDb.loadDates();
_dbMetadataLoader = localMediaDb.loadCatalogMetadata();
_dbAddressLoader = localMediaDb.loadAddresses();
_dbTrashLoader = localMediaDb.loadAllTrashDetails();
_dbVaultsLoader = localMediaDb.loadAllVaults();
_dbFavouritesLoader = localMediaDb.loadAllFavourites();
_dbCoversLoader = localMediaDb.loadAllCovers();
_dbVideoPlaybackLoader = localMediaDb.loadAllVideoPlayback();
setState(() {});
}

View file

@ -245,7 +245,7 @@ class _HomePageState extends State<HomePage> {
Future<void> _initViewerEssentials() async {
// for video playback storage
await metadataDb.init();
await localMediaDb.init();
}
bool _isViewerSourceable(AvesEntry? viewerEntry) {

View file

@ -29,6 +29,7 @@ class SupportedLocales {
'ro': 'Română',
'ru': 'Русский',
'sk': 'Slovenčina',
'sv': 'Svenska',
'tr': 'Türkçe',
'uk': 'Українська',
'vi': 'Tiếng Việt',

View file

@ -40,12 +40,12 @@ class _DbTabState extends State<DbTab> {
void _loadDatabase() {
final id = entry.id;
_dbDateLoader = metadataDb.loadDates().then((values) => values[id]);
_dbEntryLoader = metadataDb.loadEntriesById({id}).then((values) => values.firstOrNull);
_dbMetadataLoader = metadataDb.loadCatalogMetadata().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbAddressLoader = metadataDb.loadAddresses().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbTrashDetailsLoader = metadataDb.loadAllTrashDetails().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbVideoPlaybackLoader = metadataDb.loadVideoPlayback(id);
_dbDateLoader = localMediaDb.loadDates().then((values) => values[id]);
_dbEntryLoader = localMediaDb.loadEntriesById({id}).then((values) => values.firstOrNull);
_dbMetadataLoader = localMediaDb.loadCatalogMetadata().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbAddressLoader = localMediaDb.loadAddresses().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbTrashDetailsLoader = localMediaDb.loadAllTrashDetails().then((values) => values.firstWhereOrNull((row) => row.id == id));
_dbVideoPlaybackLoader = localMediaDb.loadVideoPlayback(id);
setState(() {});
}
@ -93,6 +93,15 @@ class _DbTabState extends State<DbTab> {
},
child: const Text('Untrack entry'),
),
ElevatedButton(
onPressed: () async {
final duplicates = {entry.copyWith(id: localMediaDb.nextId)};
final source = context.read<CollectionSource>();
source.addEntries(duplicates);
await localMediaDb.insertEntries(duplicates);
},
child: const Text('Duplicate entry'),
),
InfoRowGroup(
info: {
'uri': data.uri,
@ -184,7 +193,7 @@ class _DbTabState extends State<DbTab> {
ElevatedButton(
onPressed: () async {
entry.trashDetails = null;
await metadataDb.updateTrash(entry.id, entry.trashDetails);
await localMediaDb.updateTrash(entry.id, entry.trashDetails);
_loadDatabase();
},
child: const Text('Remove details'),

View file

@ -568,9 +568,12 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} else if (notification is CastNotification) {
_cast(notification.enabled);
} else if (notification is FullImageLoadedNotification) {
final viewStateController = context.read<ViewStateConductor>().getOrCreateController(notification.entry);
// microtask so that listeners do not trigger during build
scheduleMicrotask(() => viewStateController.fullImageNotifier.value = notification.image);
scheduleMicrotask(() {
if (!mounted) return;
final viewStateController = context.read<ViewStateConductor>().getController(notification.entry);
viewStateController?.fullImageNotifier.value = notification.image;
});
} else if (notification is EntryDeletedNotification) {
_onEntryRemoved(context, notification.entries);
} else if (notification is EntryMovedNotification) {

View file

@ -16,12 +16,12 @@ class DatabasePlaybackStateHandler extends PlaybackStateHandler {
@override
Future<int?> getResumeTime({required int entryId, required BuildContext context}) async {
final playback = await metadataDb.loadVideoPlayback(entryId);
final playback = await localMediaDb.loadVideoPlayback(entryId);
final resumeTime = playback?.resumeTimeMillis ?? 0;
if (resumeTime == 0) return null;
// clear on retrieval
await metadataDb.removeVideoPlayback({entryId});
await localMediaDb.removeVideoPlayback({entryId});
switch (settings.videoResumptionMode) {
case VideoResumptionMode.never:
@ -54,14 +54,14 @@ class DatabasePlaybackStateHandler extends PlaybackStateHandler {
@override
Future<void> saveResumeTime({required int entryId, required int position, required double progress}) async {
if (resumeTimeSaveMinProgress < progress && progress < resumeTimeSaveMaxProgress) {
await metadataDb.addVideoPlayback({
await localMediaDb.addVideoPlayback({
VideoPlaybackRow(
entryId: entryId,
resumeTimeMillis: position,
)
});
} else {
await metadataDb.removeVideoPlayback({entryId});
await localMediaDb.removeVideoPlayback({entryId});
}
}
}

View file

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: b1595874fbc8f7a50da90f5d8f327bb0bfd6a95dc906c390efe991540c3b54aa
sha256: "9371d13b8ee442e3bfc08a24e3a1b3742c839abbfaf5eef11b79c4b862c89bf7"
url: "https://pub.dev"
source: hosted
version: "1.3.40"
version: "1.3.41"
async:
dependency: transitive
description:
@ -68,42 +68,42 @@ packages:
dependency: "direct main"
description:
name: firebase_core
sha256: "3187f4f8e49968573fd7403011dca67ba95aae419bc0d8131500fae160d94f92"
sha256: "06537da27db981947fa535bb91ca120b4e9cb59cb87278dbdde718558cafc9ff"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "3.4.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02"
sha256: f7d7180c7f99babd4b4c517754d41a09a4943a0f7a69b65c894ca5c68ba66315
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.2.1"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: e8d1e22de72cb21cdcfc5eed7acddab3e99cd83f3b317f54f7a96c32f25fd11e
sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88"
url: "https://pub.dev"
source: hosted
version: "2.17.4"
version: "2.17.5"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: "30260e1b8ad1464b41ca4531b44ce63d752daaf2f12c92ca6cdcd82b270abecc"
sha256: "4c9872020c0d97a161362ee6af7000cfdb8666234ddc290a15252ad379bb235a"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
version: "4.1.0"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: a75e1826d92ea4e86e4a753c7b5d64b844a362676fa653185f1581c859186d18
sha256: ede8a199ff03378857d3c8cbb7fa58d37c27bb5a6b75faf8415ff6925dcaae2a
url: "https://pub.dev"
source: hosted
version: "3.6.40"
version: "3.6.41"
flutter:
dependency: "direct main"
description: flutter
@ -264,10 +264,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.5"
web:
dependency: transitive
description:

View file

@ -89,10 +89,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "93429694c9253d2871b3af80cf11b3cbb5c65660d402ed7bf69854ce4a089f82"
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
url: "https://pub.dev"
source: hosted
version: "10.1.1"
version: "10.1.2"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -113,10 +113,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
file:
dependency: transitive
description:
@ -158,10 +158,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
url: "https://pub.dev"
source: hosted
version: "2.0.21"
version: "2.0.22"
flutter_web_plugins:
dependency: transitive
description: flutter
@ -203,26 +203,26 @@ packages:
dependency: "direct main"
description:
name: google_maps_flutter
sha256: "1998c58100487af9c645ba05961e7eab8b20795611e67b1296311746a55037d4"
sha256: "2e302fa3aaf4e2a297f0342d83ebc5e8e9f826e9a716aef473fe7f404ec630a7"
url: "https://pub.dev"
source: hosted
version: "2.8.0"
version: "2.9.0"
google_maps_flutter_android:
dependency: "direct main"
description:
name: google_maps_flutter_android
sha256: f34fec69957739245d732667429a6831f97419261f6f3c31cc6489eb3976444e
sha256: "60a005bf1ba8d178144e442f6e2d734b0ffc2cc800a05415388472f934ad6d6a"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
version: "2.14.4"
google_maps_flutter_ios:
dependency: transitive
description:
name: google_maps_flutter_ios
sha256: bfa2583bfb2cf2bcd85c0d12366a2a4f38fff70d75bc1d089685677bd5d3acec
sha256: "3a484846fc56f15e47e3de1f5ea80a7ff2b31721d2faa88f390f3b3cf580c953"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
google_maps_flutter_platform_interface:
dependency: "direct main"
description:
@ -456,10 +456,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev"
source: hosted
version: "5.5.3"
version: "5.5.4"
win32_registry:
dependency: transitive
description:

View file

@ -74,10 +74,10 @@ packages:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.5"
dbus:
dependency: transitive
description:
@ -98,10 +98,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
fixnum:
dependency: transitive
description:
@ -180,10 +180,10 @@ packages:
dependency: "direct main"
description:
name: media_kit
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62"
url: "https://pub.dev"
source: hosted
version: "1.1.10+1"
version: "1.1.11"
media_kit_libs_android_video:
dependency: "direct main"
description:
@ -196,18 +196,18 @@ packages:
dependency: "direct main"
description:
name: media_kit_native_event_loop
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e
sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40"
url: "https://pub.dev"
source: hosted
version: "1.0.8"
version: "1.0.9"
media_kit_video:
dependency: "direct main"
description:
name: media_kit_video
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882
sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f"
url: "https://pub.dev"
source: hosted
version: "1.2.4"
version: "1.2.5"
meta:
dependency: transitive
description:
@ -220,10 +220,10 @@ packages:
dependency: transitive
description:
name: package_info_plus
sha256: "4de6c36df77ffbcef0a5aefe04669d33f2d18397fea228277b852a2d4e58e860"
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
url: "https://pub.dev"
source: hosted
version: "8.0.1"
version: "8.0.2"
package_info_plus_platform_interface:
dependency: transitive
description:
@ -345,10 +345,10 @@ packages:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
version: "3.2.0"
term_glyph:
dependency: transitive
description:
@ -401,18 +401,18 @@ packages:
dependency: transitive
description:
name: volume_controller
sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9"
sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e
url: "https://pub.dev"
source: hosted
version: "2.0.7"
version: "2.0.8"
wakelock_plus:
dependency: transitive
description:
name: wakelock_plus
sha256: "4fa83a128b4127619e385f686b4f080a5d2de46cff8e8c94eccac5fcf76550e5"
sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484
url: "https://pub.dev"
source: hosted
version: "1.2.7"
version: "1.2.8"
wakelock_plus_platform_interface:
dependency: transitive
description:
@ -425,18 +425,18 @@ packages:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "1.0.0"
win32:
dependency: transitive
description:
name: win32
sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev"
source: hosted
version: "5.5.3"
version: "5.5.4"
xml:
dependency: transitive
description:

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: b1595874fbc8f7a50da90f5d8f327bb0bfd6a95dc906c390efe991540c3b54aa
sha256: "9371d13b8ee442e3bfc08a24e3a1b3742c839abbfaf5eef11b79c4b862c89bf7"
url: "https://pub.dev"
source: hosted
version: "1.3.40"
version: "1.3.41"
_macros:
dependency: transitive
description: dart
@ -157,10 +157,10 @@ packages:
dependency: transitive
description:
name: bidi
sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63"
sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d"
url: "https://pub.dev"
source: hosted
version: "2.0.10"
version: "2.0.12"
boolean_selector:
dependency: transitive
description:
@ -215,10 +215,10 @@ packages:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "3e7d1d9dbae40ae82cbe6c23c518f0c4ffe32764ee9749b9a99d32cbac8734f6"
sha256: "2056db5241f96cdc0126bd94459fc4cdc13876753768fc7a31c425e50a7177d0"
url: "https://pub.dev"
source: hosted
version: "6.0.4"
version: "6.0.5"
connectivity_plus_platform_interface:
dependency: transitive
description:
@ -247,18 +247,18 @@ packages:
dependency: transitive
description:
name: coverage
sha256: "576aaab8b1abdd452e0f656c3e73da9ead9d7880e15bdc494189d9c1a1baf0db"
sha256: "7b594a150942e0d3be99cd45a1d0b5caff27ba5a27f292ed8e8d904ba3f167b5"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.9.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.5"
csslib:
dependency: transitive
description:
@ -303,10 +303,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "93429694c9253d2871b3af80cf11b3cbb5c65660d402ed7bf69854ce4a089f82"
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
url: "https://pub.dev"
source: hosted
version: "10.1.1"
version: "10.1.2"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -368,10 +368,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
ffmpeg_kit_flutter:
dependency: transitive
description:
@ -401,42 +401,42 @@ packages:
dependency: transitive
description:
name: firebase_core
sha256: "3187f4f8e49968573fd7403011dca67ba95aae419bc0d8131500fae160d94f92"
sha256: "06537da27db981947fa535bb91ca120b4e9cb59cb87278dbdde718558cafc9ff"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "3.4.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02"
sha256: f7d7180c7f99babd4b4c517754d41a09a4943a0f7a69b65c894ca5c68ba66315
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.2.1"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: e8d1e22de72cb21cdcfc5eed7acddab3e99cd83f3b317f54f7a96c32f25fd11e
sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88"
url: "https://pub.dev"
source: hosted
version: "2.17.4"
version: "2.17.5"
firebase_crashlytics:
dependency: transitive
description:
name: firebase_crashlytics
sha256: "30260e1b8ad1464b41ca4531b44ce63d752daaf2f12c92ca6cdcd82b270abecc"
sha256: "4c9872020c0d97a161362ee6af7000cfdb8666234ddc290a15252ad379bb235a"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
version: "4.1.0"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: a75e1826d92ea4e86e4a753c7b5d64b844a362676fa653185f1581c859186d18
sha256: ede8a199ff03378857d3c8cbb7fa58d37c27bb5a6b75faf8415ff6925dcaae2a
url: "https://pub.dev"
source: hosted
version: "3.6.40"
version: "3.6.41"
fixnum:
dependency: transitive
description:
@ -457,10 +457,10 @@ packages:
dependency: transitive
description:
name: flex_seed_scheme
sha256: cc08c81879ecfd2ab840664ce4770980da0b8a319e35f51bcf763849b7f7596b
sha256: "86470c8dc470f55dd3e28a6d30e3253a1c176df32903263d7daeabfc0c77dbd4"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.2.0"
floating:
dependency: "direct main"
description:
@ -545,18 +545,18 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: "2e8a801b1ded5ea001a4529c97b1f213dcb11c6b20668e081cafb23468593514"
sha256: a23c41ee57573e62fc2190a1f36a0480c4d90bde3a8a8d7126e5d5992fb53fb7
url: "https://pub.dev"
source: hosted
version: "0.7.3"
version: "0.7.3+1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
url: "https://pub.dev"
source: hosted
version: "2.0.21"
version: "2.0.22"
flutter_staggered_animations:
dependency: "direct main"
description:
@ -640,26 +640,26 @@ packages:
dependency: transitive
description:
name: google_maps_flutter
sha256: "1998c58100487af9c645ba05961e7eab8b20795611e67b1296311746a55037d4"
sha256: "2e302fa3aaf4e2a297f0342d83ebc5e8e9f826e9a716aef473fe7f404ec630a7"
url: "https://pub.dev"
source: hosted
version: "2.8.0"
version: "2.9.0"
google_maps_flutter_android:
dependency: transitive
description:
name: google_maps_flutter_android
sha256: f34fec69957739245d732667429a6831f97419261f6f3c31cc6489eb3976444e
sha256: "60a005bf1ba8d178144e442f6e2d734b0ffc2cc800a05415388472f934ad6d6a"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
version: "2.14.4"
google_maps_flutter_ios:
dependency: transitive
description:
name: google_maps_flutter_ios
sha256: bfa2583bfb2cf2bcd85c0d12366a2a4f38fff70d75bc1d089685677bd5d3acec
sha256: "3a484846fc56f15e47e3de1f5ea80a7ff2b31721d2faa88f390f3b3cf580c953"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
google_maps_flutter_platform_interface:
dependency: transitive
description:
@ -808,10 +808,10 @@ packages:
dependency: transitive
description:
name: local_auth_android
sha256: e99c44ca0bce08f26f25e2a2e07d3b443d69986e1c3acf67c1449f7d847e3625
sha256: e9a3c321e94359a552b1bdd0f98f79885f2b3e27234d270f9bef5cd82b29340c
url: "https://pub.dev"
source: hosted
version: "1.0.43"
version: "1.0.44"
local_auth_darwin:
dependency: transitive
description:
@ -896,10 +896,10 @@ packages:
dependency: transitive
description:
name: media_kit
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62"
url: "https://pub.dev"
source: hosted
version: "1.1.10+1"
version: "1.1.11"
media_kit_libs_android_video:
dependency: transitive
description:
@ -912,18 +912,18 @@ packages:
dependency: transitive
description:
name: media_kit_native_event_loop
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e
sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40"
url: "https://pub.dev"
source: hosted
version: "1.0.8"
version: "1.0.9"
media_kit_video:
dependency: "direct overridden"
description:
name: media_kit_video
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882
sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f"
url: "https://pub.dev"
source: hosted
version: "1.2.4"
version: "1.2.5"
meta:
dependency: transitive
description:
@ -944,10 +944,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
motion_sensors:
dependency: transitive
description:
@ -969,10 +969,10 @@ packages:
dependency: "direct main"
description:
name: network_info_plus
sha256: "0754302af09e58a60c7cd6800029d00e03c68f289a19fd9df31941ab91da8903"
sha256: "6a31fa47c1f6e240f1b60de0a57d65a092ac1af7515247660f03643576984eb8"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
version: "6.0.1"
network_info_plus_platform_interface:
dependency: transitive
description:
@ -1017,10 +1017,10 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "4de6c36df77ffbcef0a5aefe04669d33f2d18397fea228277b852a2d4e58e860"
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
url: "https://pub.dev"
source: hosted
version: "8.0.1"
version: "8.0.2"
package_info_plus_platform_interface:
dependency: transitive
description:
@ -1098,10 +1098,10 @@ packages:
dependency: "direct main"
description:
name: pdf
sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0"
sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07"
url: "https://pub.dev"
source: hosted
version: "3.11.0"
version: "3.11.1"
pdf_widget_wrapper:
dependency: transitive
description:
@ -1130,10 +1130,10 @@ packages:
dependency: transitive
description:
name: permission_handler_android
sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
url: "https://pub.dev"
source: hosted
version: "12.0.8"
version: "12.0.12"
permission_handler_apple:
dependency: transitive
description:
@ -1146,10 +1146,10 @@ packages:
dependency: transitive
description:
name: permission_handler_html
sha256: "6cac773d389e045a8d4f85418d07ad58ef9e42a56e063629ce14c4c26344de24"
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
url: "https://pub.dev"
source: hosted
version: "0.1.2"
version: "0.1.3+2"
permission_handler_platform_interface:
dependency: transitive
description:
@ -1330,34 +1330,34 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.5.2"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
shared_preferences_platform_interface:
dependency: "direct dev"
description:
@ -1370,18 +1370,18 @@ packages:
dependency: transitive
description:
name: shared_preferences_web
sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2"
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
shelf:
dependency: "direct main"
description:
@ -1431,10 +1431,10 @@ packages:
dependency: transitive
description:
name: source_map_stack_trace
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
source_maps:
dependency: transitive
description:
@ -1471,10 +1471,10 @@ packages:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
version: "2.5.4+2"
stack_trace:
dependency: "direct main"
description:
@ -1528,10 +1528,10 @@ packages:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
version: "3.2.0"
term_glyph:
dependency: transitive
description:
@ -1616,10 +1616,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9"
sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab
url: "https://pub.dev"
source: hosted
version: "6.3.8"
version: "6.3.10"
url_launcher_ios:
dependency: transitive
description:
@ -1632,10 +1632,10 @@ packages:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.2.0"
url_launcher_macos:
dependency: transitive
description:
@ -1688,26 +1688,26 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.5"
volume_controller:
dependency: "direct main"
description:
name: volume_controller
sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9"
sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e
url: "https://pub.dev"
source: hosted
version: "2.0.7"
version: "2.0.8"
wakelock_plus:
dependency: transitive
description:
name: wakelock_plus
sha256: "4fa83a128b4127619e385f686b4f080a5d2de46cff8e8c94eccac5fcf76550e5"
sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484
url: "https://pub.dev"
source: hosted
version: "1.2.7"
version: "1.2.8"
wakelock_plus_platform_interface:
dependency: transitive
description:
@ -1768,10 +1768,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev"
source: hosted
version: "5.5.3"
version: "5.5.4"
win32_registry:
dependency: transitive
description:
@ -1814,4 +1814,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"
flutter: ">=3.24.1"

View file

@ -7,13 +7,13 @@ repository: https://github.com/deckerst/aves
# - play changelog: /whatsnew/whatsnew-en-US
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
version: 1.11.9+128
version: 1.11.10+129
publish_to: none
environment:
# this project bundles Flutter SDK via `flutter_wrapper`
# cf https://github.com/passsy/flutter_wrapper
flutter: 3.24.0
flutter: 3.24.1
sdk: '>=3.5.0 <4.0.0'
# use `scripts/apply_flavor_{flavor}.sh` to set the right dependencies for the flavor
@ -119,6 +119,10 @@ dependency_overrides:
# media_kit_video v1.2.4 depends on a specific old version of screen_brightness
media_kit_video: ^1.0.0
screen_brightness: ^1.0.0
# Because printing >=5.13.2 depends on web ^1.0.0 and firebase_core_web >=2.12.0 depends on web ^0.5.1, printing >=5.13.2 is incompatible with firebase_core_web >=2.12.0.
# And because firebase_core 3.3.0 depends on firebase_core_web ^2.17.4, printing >=5.13.2 is incompatible with firebase_core 3.3.0.
# So, because aves depends on both firebase_core 3.3.0 and printing 5.13.2, version solving failed.
printing: "5.13.1"
dev_dependencies:
flutter_test:

28
scripts/extract_apks_libre.sh Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
if [ ! -d "scripts" ]; then
cd ..
fi
BUNDLE="/home/tibo/Downloads/app-libre-release.aab"
APKS_FULL="/home/tibo/Downloads/app-libre-release.apks"
APKS_STRIPPED="/home/tibo/Downloads/app-libre-release_stripped.apks"
rm "$APKS_FULL"
# shellcheck disable=SC2001
OUTPUT=$(sed "s|\.aab|\.apks|" <<<"$BUNDLE")
KEYS_PATH="android/key.properties"
STORE_PATH=$(sed -n 's|.*storeFile=\(.*\)[\r\n]|\1|p' "$KEYS_PATH")
# shellcheck disable=SC1003
STORE_PW=$(sed -n 's|.*storePassword=\(.*\)[\r\n]|\1|p' "$KEYS_PATH" | sed 's|\\'\''|'\''|g')
KEY_ALIAS=$(sed -n 's|.*keyAlias=\(.*\)[\r\n]|\1|p' "$KEYS_PATH")
# shellcheck disable=SC1003
KEY_PW=$(sed -n 's|.*keyPassword=\(.*\)[\r\n]|\1|p' "$KEYS_PATH" | sed 's|\\'\''|'\''|g')
echo "$BUNDLE -> $OUTPUT"
bundletool build-apks --bundle="$BUNDLE" --output="$OUTPUT" \
--ks="$STORE_PATH" --ks-pass="pass:$STORE_PW" \
--ks-key-alias="$KEY_ALIAS" --key-pass="pass:$KEY_PW"
../apkstripper "$APKS_FULL" "$APKS_STRIPPED"

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
import 'package:aves/model/covers.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/filters.dart';
@ -10,7 +10,7 @@ import 'package:aves/model/vaults/details.dart';
import 'package:flutter/foundation.dart';
import 'package:test/fake.dart';
class FakeMetadataDb extends Fake implements MetadataDb {
class FakeAvesDb extends Fake implements LocalMediaDb {
static int _lastId = 0;
@override
@ -28,11 +28,14 @@ class FakeMetadataDb extends Fake implements MetadataDb {
Future<Set<AvesEntry>> loadEntries({int? origin, String? directory}) => SynchronousFuture({});
@override
Future<void> saveEntries(Set<AvesEntry> entries) => SynchronousFuture(null);
Future<void> insertEntries(Set<AvesEntry> entries) => SynchronousFuture(null);
@override
Future<void> updateEntry(int id, AvesEntry entry) => SynchronousFuture(null);
@override
Future<Set<AvesEntry>> searchLiveDuplicates(int origin, Set<AvesEntry>? entries) => SynchronousFuture({});
// date taken
@override

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/availability.dart';
import 'package:aves/model/covers.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db.dart';
import 'package:aves/model/entry/extensions/favourites.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/album.dart';
@ -35,7 +35,7 @@ import '../fake/availability.dart';
import '../fake/device_service.dart';
import '../fake/media_fetch_service.dart';
import '../fake/media_store_service.dart';
import '../fake/metadata_db.dart';
import '../fake/db.dart';
import '../fake/metadata_fetch_service.dart';
import '../fake/report_service.dart';
import '../fake/storage_service.dart';
@ -58,7 +58,7 @@ void main() {
// specify Posix style path context for consistent behaviour when running tests on Windows
getIt.registerLazySingleton<p.Context>(() => p.Context(style: p.Style.posix));
getIt.registerLazySingleton<AvesAvailability>(FakeAvesAvailability.new);
getIt.registerLazySingleton<MetadataDb>(FakeMetadataDb.new);
getIt.registerLazySingleton<LocalMediaDb>(FakeAvesDb.new);
getIt.registerLazySingleton<AppService>(FakeAppService.new);
getIt.registerLazySingleton<DeviceService>(FakeDeviceService.new);

View file

@ -1,5 +1,3 @@
In v1.11.9:
- peruse more options to tag or move via quick actions
- read long descriptions right from the overlay
- sort videos by duration
In v1.11.10:
- enjoy the app in Swedish
Full changelog available on GitHub