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: push:
branches: branches:
- develop - develop
pull_request:
types: [ opened, synchronize, reopened ]
permissions:
contents: read
jobs: jobs:
build: build:
name: Check code quality. name: Check code quality.
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- name: Clone the repository. - name: Clone the repository.
uses: actions/checkout@v4 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get packages for the Flutter project. - name: Get packages for the Flutter project.
run: scripts/pub_get_all.sh 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. name: Build and release artifacts.
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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: with:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
- name: Clone the repository. - name: Clone the repository.
uses: actions/checkout@v4 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get packages for the Flutter project. - name: Get packages for the Flutter project.
run: scripts/pub_get_all.sh run: scripts/pub_get_all.sh
@ -66,14 +71,14 @@ jobs:
AVES_GOOGLE_API_KEY: ${{ secrets.AVES_GOOGLE_API_KEY }} AVES_GOOGLE_API_KEY: ${{ secrets.AVES_GOOGLE_API_KEY }}
- name: Create a release with the APK and App Bundle. - name: Create a release with the APK and App Bundle.
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
with: with:
artifacts: "outputs/*" artifacts: "outputs/*"
body: "[Changelog](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ github.ref_name }})" body: "[Changelog](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ github.ref_name }})"
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle - name: Upload app bundle
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with: with:
name: appbundle name: appbundle
path: outputs/app-play-release.aab path: outputs/app-play-release.aab
@ -83,15 +88,20 @@ jobs:
needs: [ build ] needs: [ build ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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. - name: Get appbundle from artifacts.
uses: actions/download-artifact@v4 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: appbundle name: appbundle
- name: Release app to beta channel. - name: Release app to beta channel.
uses: r0adkll/upload-google-play@v1.1.3 uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb # v1.1.3
with: with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }} serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: deckers.thibault.aves 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="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 ## <a id="v1.11.9"></a>[v1.11.9] - 2024-08-07
### Added ### Added

View file

@ -76,7 +76,7 @@
--> -->
<uses-sdk tools:overrideLibrary="com.arthenica.ffmpegkit.flutter" /> <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> <queries>
<intent> <intent>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -92,7 +92,7 @@
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
</intent> </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= if appropriate intents are not declared, cf https://pub.dev/packages/url_launcher#configuration=
--> -->
<!-- to open https URLs --> <!-- to open https URLs -->

View file

@ -96,7 +96,7 @@ object PermissionManager {
segments.volumePath?.let { volumePath -> segments.volumePath?.let { volumePath ->
val dirSet = dirsPerVolume[volumePath] ?: HashSet() val dirSet = dirsPerVolume[volumePath] ?: HashSet()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 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 val relativeDir = segments.relativeDir
if (relativeDir != null) { if (relativeDir != null) {
val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() } val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() }
@ -172,7 +172,6 @@ object PermissionManager {
val accessibleDirs = HashSet(getGrantedDirs(context)) val accessibleDirs = HashSet(getGrantedDirs(context))
accessibleDirs.addAll(context.getExternalFilesDirs(null).filterNotNull().map { it.path }) 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 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 // from API 30 / Android 11 / R, any storage requires access permission
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { 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)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isMediaStoreContentUri(uri)) {
val path = uri.path val path = uri.path
path ?: return uri 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)) { if (path.startsWith(IMAGE_PATH_ROOT) || path.startsWith(VIDEO_PATH_ROOT)) {
// "Caller must hold ACCESS_MEDIA_LOCATION permission to access original" // "Caller must hold ACCESS_MEDIA_LOCATION permission to access original"
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) { if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {

View file

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

View file

@ -6808,6 +6808,11 @@ public class ExifInterfaceFork {
} }
firstIfdOffset -= 8; firstIfdOffset -= 8;
if (firstIfdOffset > 0) { if (firstIfdOffset > 0) {
// TLAD start
if (firstIfdOffset > ATTRIBUTE_SIZE_DANGER_THRESHOLD) {
throw new IOException("dangerous IFD offset=" + firstIfdOffset);
}
// TLAD end
dataInputStream.skipFully(firstIfdOffset); 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>. <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": "Ende festlegen",
"@videoRepeatActionSetEnd": {}, "@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "In Sammlung anzeigen", "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": "वॉल्ट को कॉन्फ़िगर करने के लिए प्रमाणीकरण करें",
"@authenticateToConfigureVault": {}, "@authenticateToConfigureVault": {},
"renameAlbumDialogLabelAlreadyExistsHelper": "डायरेक्टरी पहले से मौजूद", "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": "Pilih Penyimpanan",
"@selectStorageVolumeDialogTitle": {}, "@selectStorageVolumeDialogTitle": {},
"setHomeCustom": "Kustom", "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": "Hash",
"@renameProcessorHash": {}, "@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Forza numeri arabi", "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": {}, "@chipActionFilterIn": {},
"filterAspectRatioPortraitLabel": "縦向き", "filterAspectRatioPortraitLabel": "縦向き",
"@filterAspectRatioPortraitLabel": {}, "@filterAspectRatioPortraitLabel": {},
"filterNoAddressLabel": "位置情報なし", "filterNoAddressLabel": "アドレスなし",
"@filterNoAddressLabel": {}, "@filterNoAddressLabel": {},
"keepScreenOnVideoPlayback": "動画再生時", "keepScreenOnVideoPlayback": "動画再生時",
"@keepScreenOnVideoPlayback": {}, "@keepScreenOnVideoPlayback": {},
@ -1370,5 +1370,11 @@
"cropAspectRatioOriginal": "オリジナル", "cropAspectRatioOriginal": "オリジナル",
"@cropAspectRatioOriginal": {}, "@cropAspectRatioOriginal": {},
"stopTooltip": "停止", "stopTooltip": "停止",
"@stopTooltip": {} "@stopTooltip": {},
"explorerPageTitle": "エクスプローラー",
"@explorerPageTitle": {},
"chipActionGoToExplorerPage": "エクスプローラーで表示",
"@chipActionGoToExplorerPage": {},
"filterLocatedLabel": "位置情報あり",
"@filterLocatedLabel": {}
} }

View file

@ -1378,5 +1378,17 @@
"chipActionGoToExplorerPage": "Mostrar no Explorador", "chipActionGoToExplorerPage": "Mostrar no Explorador",
"@chipActionGoToExplorerPage": {}, "@chipActionGoToExplorerPage": {},
"explorerPageTitle": "Explorador", "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": "Setează sfârșitul",
"@videoRepeatActionSetEnd": {}, "@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "Afișați în colecție", "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('Stephan Paternotte', 'stephan@paternottes.net'),
Contributor('Tung Anh', 'buihuutunganh2007@gmail.com'), Contributor('Tung Anh', 'buihuutunganh2007@gmail.com'),
Contributor('Adrien N', 'adriennathaniel1999@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('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese // Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese // Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
@ -113,8 +118,6 @@ class Contributors {
// Contributor('Subham Jena', 'subhamjena8465@gmail.com'), // Odia // Contributor('Subham Jena', 'subhamjena8465@gmail.com'), // Odia
// Contributor('Prasanta-Hembram', 'Prasantahembram720@gmail.com'), // Santali // Contributor('Prasanta-Hembram', 'Prasantahembram720@gmail.com'), // Santali
// Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian // Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian
// Contributor('Shift18', 'bribable.lawyer@posteo.net'), // Swedish
// Contributor('Andreas Håll', 'ante_skalman@hotmail.com'), // Swedish
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai // Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
}; };
} }

View file

@ -29,10 +29,10 @@ class AppInventory {
areAppNamesReadyNotifier.value = false; areAppNamesReadyNotifier.value = false;
} }
bool isPotentialAppDir(String dir) => _potentialAppDirs.contains(dir); bool isPotentialAppDir(String dir) => _potentialAppDirs.contains(Package.normalizePotentialDir(dir));
String? getAlbumAppPackageName(String albumPath) { 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)); final package = _launcherPackages.firstWhereOrNull((v) => v.potentialDirs.contains(dir));
return package?.packageName; return package?.packageName;
} }
@ -71,7 +71,11 @@ class Package {
currentLabel, currentLabel,
englishLabel, englishLabel,
...ownedDirs, ...ownedDirs,
].whereNotNull().toSet(); ].whereNotNull().map(normalizePotentialDir).toSet();
static String normalizePotentialDir(String dir) {
return dir.replaceAll('_', ' ').trim().toLowerCase();
}
@override @override
String toString() => '$runtimeType#${shortHash(this)}{packageName=$packageName, categoryLauncher=$categoryLauncher, isSystem=$isSystem, currentLabel=$currentLabel, englishLabel=$englishLabel, ownedDirs=$ownedDirs}'; 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(); Covers._private();
Future<void> init() async { Future<void> init() async {
_rows = await metadataDb.loadAllCovers(); _rows = await localMediaDb.loadAllCovers();
} }
int get count => _rows.length; int get count => _rows.length;
@ -59,7 +59,7 @@ class Covers {
final oldRows = _rows.where((row) => row.filter == filter).toSet(); final oldRows = _rows.where((row) => row.filter == filter).toSet();
_rows.removeAll(oldRows); _rows.removeAll(oldRows);
await metadataDb.removeCovers({filter}); await localMediaDb.removeCovers({filter});
final oldRow = oldRows.firstOrNull; final oldRow = oldRows.firstOrNull;
final oldEntry = oldRow?.entryId; final oldEntry = oldRow?.entryId;
@ -74,7 +74,7 @@ class Covers {
color: color, color: color,
); );
_rows.add(row); _rows.add(row);
await metadataDb.addCovers({row}); await localMediaDb.addCovers({row});
} }
if (oldEntry != entryId) _entryChangeStreamController.add({filter}); if (oldEntry != entryId) _entryChangeStreamController.add({filter});
@ -103,7 +103,7 @@ class Covers {
} }
Future<void> clear() async { Future<void> clear() async {
await metadataDb.clearCovers(); await localMediaDb.clearCovers();
_rows.clear(); _rows.clear();
_entryChangeStreamController.add(null); _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/vaults/details.dart';
import 'package:aves/model/viewer/video_playback.dart'; import 'package:aves/model/viewer/video_playback.dart';
abstract class MetadataDb { abstract class LocalMediaDb {
int get nextId; int get nextId;
Future<void> init(); Future<void> init();
@ -27,12 +27,14 @@ abstract class MetadataDb {
Future<Set<AvesEntry>> loadEntriesById(Set<int> ids); 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<void> updateEntry(int id, AvesEntry entry);
Future<Set<AvesEntry>> searchLiveEntries(String query, {int? limit}); Future<Set<AvesEntry>> searchLiveEntries(String query, {int? limit});
Future<Set<AvesEntry>> searchLiveDuplicates(int origin, Set<AvesEntry>? entries);
// date taken // date taken
Future<void> clearDates(); Future<void> clearDates();

View file

@ -1,8 +1,8 @@
import 'dart:io'; import 'dart:io';
import 'package:aves/model/covers.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/db/db_metadata_sqflite_upgrade.dart'; import 'package:aves/model/db/db_sqflite_upgrade.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/favourites.dart'; import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
@ -16,7 +16,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
class SqfliteMetadataDb implements MetadataDb { class SqfliteLocalMediaDb implements LocalMediaDb {
late Database _db; late Database _db;
Future<String> get path async => pContext.join(await getDatabasesPath(), 'metadata.db'); Future<String> get path async => pContext.join(await getDatabasesPath(), 'metadata.db');
@ -108,11 +108,11 @@ class SqfliteMetadataDb implements MetadataDb {
', resumeTimeMillis INTEGER' ', resumeTimeMillis INTEGER'
')'); ')');
}, },
onUpgrade: MetadataDbUpgrader.upgradeDb, onUpgrade: LocalMediaDbUpgrader.upgradeDb,
version: 11, 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; _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); Future<Set<AvesEntry>> loadEntriesById(Set<int> ids) => _getByIds(ids, entryTable, AvesEntry.fromMap);
@override @override
Future<void> saveEntries(Set<AvesEntry> entries) async { Future<void> insertEntries(Set<AvesEntry> entries) async {
if (entries.isEmpty) return; if (entries.isEmpty) return;
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
final batch = _db.batch(); final batch = _db.batch();
@ -246,6 +246,28 @@ class SqfliteMetadataDb implements MetadataDb {
return rows.map(AvesEntry.fromMap).toSet(); 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 // date taken
@override @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:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
class MetadataDbUpgrader { class LocalMediaDbUpgrader {
static const entryTable = SqfliteMetadataDb.entryTable; static const entryTable = SqfliteLocalMediaDb.entryTable;
static const dateTakenTable = SqfliteMetadataDb.dateTakenTable; static const dateTakenTable = SqfliteLocalMediaDb.dateTakenTable;
static const metadataTable = SqfliteMetadataDb.metadataTable; static const metadataTable = SqfliteLocalMediaDb.metadataTable;
static const addressTable = SqfliteMetadataDb.addressTable; static const addressTable = SqfliteLocalMediaDb.addressTable;
static const favouriteTable = SqfliteMetadataDb.favouriteTable; static const favouriteTable = SqfliteLocalMediaDb.favouriteTable;
static const coverTable = SqfliteMetadataDb.coverTable; static const coverTable = SqfliteLocalMediaDb.coverTable;
static const vaultTable = SqfliteMetadataDb.vaultTable; static const vaultTable = SqfliteLocalMediaDb.vaultTable;
static const trashTable = SqfliteMetadataDb.trashTable; static const trashTable = SqfliteLocalMediaDb.trashTable;
static const videoPlaybackTable = SqfliteMetadataDb.videoPlaybackTable; static const videoPlaybackTable = SqfliteLocalMediaDb.videoPlaybackTable;
// warning: "ALTER TABLE ... RENAME COLUMN ..." is not supported // warning: "ALTER TABLE ... RENAME COLUMN ..." is not supported
// on SQLite <3.25.0, bundled on older Android devices // 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 (isFlipped is bool) this.isFlipped = isFlipped;
if (persist) { if (persist) {
await metadataDb.saveEntries({this}); await localMediaDb.updateEntry(id, this);
if (catalogMetadata != null) await metadataDb.saveCatalogMetadata({catalogMetadata!}); if (catalogMetadata != null) await localMediaDb.saveCatalogMetadata({catalogMetadata!});
} }
await _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); await _onVisualFieldChanged(oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped);
@ -451,7 +451,7 @@ class AvesEntry with AvesEntryBase {
_tags = null; _tags = null;
if (persist) { if (persist) {
await metadataDb.removeIds({id}, dataTypes: dataTypes); await localMediaDb.removeIds({id}, dataTypes: dataTypes);
} }
final updatedEntry = await mediaFetchService.getEntry(uri, mimeType); final updatedEntry = await mediaFetchService.getEntry(uri, mimeType);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,7 @@ import 'package:aves_model/aves_model.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:permission_handler/permission_handler.dart';
class AnalysisService { class AnalysisService {
static const _platform = MethodChannel('deckers.thibault/aves/analysis'); 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 { 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' : ''}'); await reportService.log('Start analysis service${entryIds != null ? ' for ${entryIds.length} items' : ''}');
try { try {
await _platform.invokeMethod('startAnalysis', <String, dynamic>{ await _platform.invokeMethod('startAnalysis', <String, dynamic>{
@ -48,7 +53,7 @@ Future<void> _init() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
initPlatformServices(); initPlatformServices();
await androidFileUtils.init(); await androidFileUtils.init();
await metadataDb.init(); await localMediaDb.init();
await device.init(); await device.init();
await mobileServices.init(); await mobileServices.init();
await settings.init(monitorPlatformSettings: false); await settings.init(monitorPlatformSettings: false);

View file

@ -43,7 +43,7 @@ class PlatformAppService implements AppService {
'com.sony.playmemories.mobile': {'Imaging Edge Mobile'}, 'com.sony.playmemories.mobile': {'Imaging Edge Mobile'},
'nekox.messenger': {'NekoX'}, 'nekox.messenger': {'NekoX'},
'org.telegram.messenger': {'Telegram Images', 'Telegram Video'}, '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 @override

View file

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

View file

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

View file

@ -71,7 +71,6 @@ class AvesApp extends StatefulWidget {
'or', // Odia 'or', // Odia
'sat', // Santali 'sat', // Santali
'sl', // Slovenian 'sl', // Slovenian
'sv', // Swedish
'th', // Thai 'th', // Thai
}.map(Locale.new).toSet(); }.map(Locale.new).toSet();
static final List<Locale> supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList(); 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 { Future<void> _onAnalysisCompletion() async {
debugPrint('Analysis completed'); debugPrint('Analysis completed');
await _mediaStoreSource.loadCatalogMetadata(); if (_mediaStoreSource.initState != SourceInitializationState.none) {
await _mediaStoreSource.loadAddresses(); await _mediaStoreSource.loadCatalogMetadata();
_mediaStoreSource.updateDerivedFilters(); await _mediaStoreSource.loadAddresses();
_mediaStoreSource.updateDerivedFilters();
}
} }
void _onError(String? error) => reportService.recordError(error, null); 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/list_details_theme.dart';
import 'package:aves/widgets/collection/grid/section_layout.dart'; import 'package:aves/widgets/collection/grid/section_layout.dart';
import 'package:aves/widgets/collection/grid/tile.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/draggable_scrollbar/scrollbar.dart';
import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/behaviour/routes.dart';
@ -587,7 +588,13 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
valueListenable: collection.source.stateNotifier, valueListenable: collection.source.stateNotifier,
builder: (context, sourceState, child) { builder: (context, sourceState, child) {
if (sourceState == SourceState.loading) { 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>( return FutureBuilder<bool>(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -568,9 +568,12 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} else if (notification is CastNotification) { } else if (notification is CastNotification) {
_cast(notification.enabled); _cast(notification.enabled);
} else if (notification is FullImageLoadedNotification) { } else if (notification is FullImageLoadedNotification) {
final viewStateController = context.read<ViewStateConductor>().getOrCreateController(notification.entry);
// microtask so that listeners do not trigger during build // 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) { } else if (notification is EntryDeletedNotification) {
_onEntryRemoved(context, notification.entries); _onEntryRemoved(context, notification.entries);
} else if (notification is EntryMovedNotification) { } else if (notification is EntryMovedNotification) {

View file

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

View file

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

View file

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

View file

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

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: b1595874fbc8f7a50da90f5d8f327bb0bfd6a95dc906c390efe991540c3b54aa sha256: "9371d13b8ee442e3bfc08a24e3a1b3742c839abbfaf5eef11b79c4b862c89bf7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.40" version: "1.3.41"
_macros: _macros:
dependency: transitive dependency: transitive
description: dart description: dart
@ -157,10 +157,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: bidi name: bidi
sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10" version: "2.0.12"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -215,10 +215,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity_plus name: connectivity_plus
sha256: "3e7d1d9dbae40ae82cbe6c23c518f0c4ffe32764ee9749b9a99d32cbac8734f6" sha256: "2056db5241f96cdc0126bd94459fc4cdc13876753768fc7a31c425e50a7177d0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.4" version: "6.0.5"
connectivity_plus_platform_interface: connectivity_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -247,18 +247,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: coverage name: coverage
sha256: "576aaab8b1abdd452e0f656c3e73da9ead9d7880e15bdc494189d9c1a1baf0db" sha256: "7b594a150942e0d3be99cd45a1d0b5caff27ba5a27f292ed8e8d904ba3f167b5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.5"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@ -303,10 +303,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "93429694c9253d2871b3af80cf11b3cbb5c65660d402ed7bf69854ce4a089f82" sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.1.1" version: "10.1.2"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -368,10 +368,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
ffmpeg_kit_flutter: ffmpeg_kit_flutter:
dependency: transitive dependency: transitive
description: description:
@ -401,42 +401,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core name: firebase_core
sha256: "3187f4f8e49968573fd7403011dca67ba95aae419bc0d8131500fae160d94f92" sha256: "06537da27db981947fa535bb91ca120b4e9cb59cb87278dbdde718558cafc9ff"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0" version: "3.4.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_platform_interface name: firebase_core_platform_interface
sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02" sha256: f7d7180c7f99babd4b4c517754d41a09a4943a0f7a69b65c894ca5c68ba66315
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.0" version: "5.2.1"
firebase_core_web: firebase_core_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: e8d1e22de72cb21cdcfc5eed7acddab3e99cd83f3b317f54f7a96c32f25fd11e sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.17.4" version: "2.17.5"
firebase_crashlytics: firebase_crashlytics:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics name: firebase_crashlytics
sha256: "30260e1b8ad1464b41ca4531b44ce63d752daaf2f12c92ca6cdcd82b270abecc" sha256: "4c9872020c0d97a161362ee6af7000cfdb8666234ddc290a15252ad379bb235a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.4" version: "4.1.0"
firebase_crashlytics_platform_interface: firebase_crashlytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics_platform_interface name: firebase_crashlytics_platform_interface
sha256: a75e1826d92ea4e86e4a753c7b5d64b844a362676fa653185f1581c859186d18 sha256: ede8a199ff03378857d3c8cbb7fa58d37c27bb5a6b75faf8415ff6925dcaae2a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.6.40" version: "3.6.41"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -457,10 +457,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flex_seed_scheme name: flex_seed_scheme
sha256: cc08c81879ecfd2ab840664ce4770980da0b8a319e35f51bcf763849b7f7596b sha256: "86470c8dc470f55dd3e28a6d30e3253a1c176df32903263d7daeabfc0c77dbd4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.2.0"
floating: floating:
dependency: "direct main" dependency: "direct main"
description: description:
@ -545,18 +545,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: "2e8a801b1ded5ea001a4529c97b1f213dcb11c6b20668e081cafb23468593514" sha256: a23c41ee57573e62fc2190a1f36a0480c4d90bde3a8a8d7126e5d5992fb53fb7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.3" version: "0.7.3+1"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.21" version: "2.0.22"
flutter_staggered_animations: flutter_staggered_animations:
dependency: "direct main" dependency: "direct main"
description: description:
@ -640,26 +640,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter name: google_maps_flutter
sha256: "1998c58100487af9c645ba05961e7eab8b20795611e67b1296311746a55037d4" sha256: "2e302fa3aaf4e2a297f0342d83ebc5e8e9f826e9a716aef473fe7f404ec630a7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.8.0" version: "2.9.0"
google_maps_flutter_android: google_maps_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_android name: google_maps_flutter_android
sha256: f34fec69957739245d732667429a6831f97419261f6f3c31cc6489eb3976444e sha256: "60a005bf1ba8d178144e442f6e2d734b0ffc2cc800a05415388472f934ad6d6a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.14.4"
google_maps_flutter_ios: google_maps_flutter_ios:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_ios name: google_maps_flutter_ios
sha256: bfa2583bfb2cf2bcd85c0d12366a2a4f38fff70d75bc1d089685677bd5d3acec sha256: "3a484846fc56f15e47e3de1f5ea80a7ff2b31721d2faa88f390f3b3cf580c953"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.0" version: "2.13.0"
google_maps_flutter_platform_interface: google_maps_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -808,10 +808,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: local_auth_android name: local_auth_android
sha256: e99c44ca0bce08f26f25e2a2e07d3b443d69986e1c3acf67c1449f7d847e3625 sha256: e9a3c321e94359a552b1bdd0f98f79885f2b3e27234d270f9bef5cd82b29340c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.43" version: "1.0.44"
local_auth_darwin: local_auth_darwin:
dependency: transitive dependency: transitive
description: description:
@ -896,10 +896,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: media_kit name: media_kit
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a" sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.10+1" version: "1.1.11"
media_kit_libs_android_video: media_kit_libs_android_video:
dependency: transitive dependency: transitive
description: description:
@ -912,18 +912,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: media_kit_native_event_loop name: media_kit_native_event_loop
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.9"
media_kit_video: media_kit_video:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
name: media_kit_video name: media_kit_video
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882 sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.4" version: "1.2.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -944,10 +944,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: mime name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.6"
motion_sensors: motion_sensors:
dependency: transitive dependency: transitive
description: description:
@ -969,10 +969,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: network_info_plus name: network_info_plus
sha256: "0754302af09e58a60c7cd6800029d00e03c68f289a19fd9df31941ab91da8903" sha256: "6a31fa47c1f6e240f1b60de0a57d65a092ac1af7515247660f03643576984eb8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.1"
network_info_plus_platform_interface: network_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1017,10 +1017,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "4de6c36df77ffbcef0a5aefe04669d33f2d18397fea228277b852a2d4e58e860" sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.0.1" version: "8.0.2"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1098,10 +1098,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: pdf name: pdf
sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0" sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.11.0" version: "3.11.1"
pdf_widget_wrapper: pdf_widget_wrapper:
dependency: transitive dependency: transitive
description: description:
@ -1130,10 +1130,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_android name: permission_handler_android
sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.0.8" version: "12.0.12"
permission_handler_apple: permission_handler_apple:
dependency: transitive dependency: transitive
description: description:
@ -1146,10 +1146,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_html name: permission_handler_html
sha256: "6cac773d389e045a8d4f85418d07ad58ef9e42a56e063629ce14c4c26344de24" sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.2" version: "0.1.3+2"
permission_handler_platform_interface: permission_handler_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1330,34 +1330,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68 sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294" sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.2"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.5.2"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1" sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.1"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -1370,18 +1370,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2" sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2" sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.1"
shelf: shelf:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1431,10 +1431,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_map_stack_trace name: source_map_stack_trace
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
source_maps: source_maps:
dependency: transitive dependency: transitive
description: description:
@ -1471,10 +1471,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4" version: "2.5.4+2"
stack_trace: stack_trace:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1528,10 +1528,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: synchronized name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0+1" version: "3.2.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -1616,10 +1616,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9" sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.8" version: "6.3.10"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -1632,10 +1632,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.2.0"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
@ -1688,26 +1688,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.4" version: "14.2.5"
volume_controller: volume_controller:
dependency: "direct main" dependency: "direct main"
description: description:
name: volume_controller name: volume_controller
sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9" sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.8"
wakelock_plus: wakelock_plus:
dependency: transitive dependency: transitive
description: description:
name: wakelock_plus name: wakelock_plus
sha256: "4fa83a128b4127619e385f686b4f080a5d2de46cff8e8c94eccac5fcf76550e5" sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.7" version: "1.2.8"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1768,10 +1768,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.3" version: "5.5.4"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
@ -1814,4 +1814,4 @@ packages:
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" 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 # - play changelog: /whatsnew/whatsnew-en-US
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt # - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
version: 1.11.9+128 version: 1.11.10+129
publish_to: none publish_to: none
environment: environment:
# this project bundles Flutter SDK via `flutter_wrapper` # this project bundles Flutter SDK via `flutter_wrapper`
# cf https://github.com/passsy/flutter_wrapper # cf https://github.com/passsy/flutter_wrapper
flutter: 3.24.0 flutter: 3.24.1
sdk: '>=3.5.0 <4.0.0' sdk: '>=3.5.0 <4.0.0'
# use `scripts/apply_flavor_{flavor}.sh` to set the right dependencies for the flavor # use `scripts/apply_flavor_{flavor}.sh` to set the right dependencies for the flavor
@ -119,6 +119,10 @@ dependency_overrides:
# media_kit_video v1.2.4 depends on a specific old version of screen_brightness # media_kit_video v1.2.4 depends on a specific old version of screen_brightness
media_kit_video: ^1.0.0 media_kit_video: ^1.0.0
screen_brightness: ^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: dev_dependencies:
flutter_test: 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/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/entry/entry.dart';
import 'package:aves/model/favourites.dart'; import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/filters.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:flutter/foundation.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
class FakeMetadataDb extends Fake implements MetadataDb { class FakeAvesDb extends Fake implements LocalMediaDb {
static int _lastId = 0; static int _lastId = 0;
@override @override
@ -28,11 +28,14 @@ class FakeMetadataDb extends Fake implements MetadataDb {
Future<Set<AvesEntry>> loadEntries({int? origin, String? directory}) => SynchronousFuture({}); Future<Set<AvesEntry>> loadEntries({int? origin, String? directory}) => SynchronousFuture({});
@override @override
Future<void> saveEntries(Set<AvesEntry> entries) => SynchronousFuture(null); Future<void> insertEntries(Set<AvesEntry> entries) => SynchronousFuture(null);
@override @override
Future<void> updateEntry(int id, AvesEntry entry) => SynchronousFuture(null); Future<void> updateEntry(int id, AvesEntry entry) => SynchronousFuture(null);
@override
Future<Set<AvesEntry>> searchLiveDuplicates(int origin, Set<AvesEntry>? entries) => SynchronousFuture({});
// date taken // date taken
@override @override

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:aves/l10n/l10n.dart'; import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/availability.dart'; import 'package:aves/model/availability.dart';
import 'package:aves/model/covers.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/entry/extensions/favourites.dart';
import 'package:aves/model/favourites.dart'; import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/album.dart';
@ -35,7 +35,7 @@ import '../fake/availability.dart';
import '../fake/device_service.dart'; import '../fake/device_service.dart';
import '../fake/media_fetch_service.dart'; import '../fake/media_fetch_service.dart';
import '../fake/media_store_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/metadata_fetch_service.dart';
import '../fake/report_service.dart'; import '../fake/report_service.dart';
import '../fake/storage_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 // 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<p.Context>(() => p.Context(style: p.Style.posix));
getIt.registerLazySingleton<AvesAvailability>(FakeAvesAvailability.new); getIt.registerLazySingleton<AvesAvailability>(FakeAvesAvailability.new);
getIt.registerLazySingleton<MetadataDb>(FakeMetadataDb.new); getIt.registerLazySingleton<LocalMediaDb>(FakeAvesDb.new);
getIt.registerLazySingleton<AppService>(FakeAppService.new); getIt.registerLazySingleton<AppService>(FakeAppService.new);
getIt.registerLazySingleton<DeviceService>(FakeDeviceService.new); getIt.registerLazySingleton<DeviceService>(FakeDeviceService.new);

View file

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