diff --git a/.github/workflows/quality-check.yml b/.github/workflows/quality-check.yml
index 13d7381be..7a9d92805 100644
--- a/.github/workflows/quality-check.yml
+++ b/.github/workflows/quality-check.yml
@@ -69,7 +69,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
+ uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -83,6 +83,6 @@ jobs:
./flutterw build apk --profile -t lib/main_play.dart --flavor play
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
+ uses: github/codeql-action/analyze@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 51945d34f..ce56eb19a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -87,7 +87,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: appbundle
path: outputs/app-play-release.aab
@@ -106,7 +106,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get appbundle from artifacts
- uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
+ uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
name: appbundle
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index afe5a84f8..753327c44 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -63,7 +63,7 @@ jobs:
# 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@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
@@ -71,6 +71,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
+ uses: github/codeql-action/upload-sarif@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13
with:
sarif_file: results.sarif
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 000000000..30299a232
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,29 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "aves (main play)",
+ "request": "launch",
+ "type": "dart",
+ "program": "lib/main_play.dart",
+ "args": [
+ "--flavor",
+ "play"
+ ]
+ },
+ {
+ "name": "aves (main play) [profile]",
+ "request": "launch",
+ "type": "dart",
+ "program": "lib/main_play.dart",
+ "args": [
+ "--flavor",
+ "play"
+ ],
+ "flutterMode": "profile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 045d54747..e03cef808 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.12.8] - 2025-03-25
+
+### Fixed
+
+- swiping images for some combinations of screen size, device pixel ratio, and image size
+
## [v1.12.7] - 2025-03-16
### Added
diff --git a/android/app/src/main/res/values-zh-rTW/strings.xml b/android/app/src/main/res/values-zh-rTW/strings.xml
index 7de20ea26..bf3e0c49f 100644
--- a/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -8,4 +8,5 @@
搜尋
媒體掃描
停止
-
\ No newline at end of file
+ 地圖
+
diff --git a/devtools_options.yaml b/devtools_options.yaml
index fa0b357c4..2bc8e05fd 100644
--- a/devtools_options.yaml
+++ b/devtools_options.yaml
@@ -1,3 +1,4 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
+ - provider: true
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/148.txt b/fastlane/metadata/android/en-US/changelogs/148.txt
new file mode 100644
index 000000000..3f381a910
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/148.txt
@@ -0,0 +1,4 @@
+In v1.12.8:
+- play more kinds of motion photos
+- enjoy the app in Galician
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/14801.txt b/fastlane/metadata/android/en-US/changelogs/14801.txt
new file mode 100644
index 000000000..3f381a910
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/14801.txt
@@ -0,0 +1,4 @@
+In v1.12.8:
+- play more kinds of motion photos
+- enjoy the app in Galician
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb
index f577ae2ac..715917fac 100644
--- a/lib/l10n/app_bg.arb
+++ b/lib/l10n/app_bg.arb
@@ -1196,9 +1196,9 @@
"@settingsNavigationDrawerAddAlbum": {},
"settingsThumbnailSectionTitle": "Миниатюри",
"@settingsThumbnailSectionTitle": {},
- "settingsThumbnailOverlayTile": "Наслагване",
+ "settingsThumbnailOverlayTile": "Повече информация",
"@settingsThumbnailOverlayTile": {},
- "settingsThumbnailOverlayPageTitle": "Наслагване",
+ "settingsThumbnailOverlayPageTitle": "Повече информация",
"@settingsThumbnailOverlayPageTitle": {},
"settingsThumbnailShowHdrIcon": "Показване на HDR икона",
"@settingsThumbnailShowHdrIcon": {},
@@ -1258,7 +1258,7 @@
"@settingsViewerMaximumBrightness": {},
"settingsMotionPhotoAutoPlay": "Автоматично възпроизвеждане на снимки с движение",
"@settingsMotionPhotoAutoPlay": {},
- "settingsViewerOverlayPageTitle": "Наслагване",
+ "settingsViewerOverlayPageTitle": "Повече информация",
"@settingsViewerOverlayPageTitle": {},
"settingsViewerShowHistogram": "Показвай хистограма",
"@settingsViewerShowHistogram": {},
@@ -1272,7 +1272,7 @@
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
"settingsViewerQuickActionEmpty": "Без бутони",
"@settingsViewerQuickActionEmpty": {},
- "settingsViewerOverlayTile": "Наслагване",
+ "settingsViewerOverlayTile": "Повече информация",
"@settingsViewerOverlayTile": {},
"settingsViewerShowRatingTags": "Показване на рейтинг и тагове",
"@settingsViewerShowRatingTags": {},
diff --git a/lib/l10n/app_ro.arb b/lib/l10n/app_ro.arb
index 35304743d..ddd96552b 100644
--- a/lib/l10n/app_ro.arb
+++ b/lib/l10n/app_ro.arb
@@ -1598,5 +1598,15 @@
"newAlbumDialogAlbumAlreadyExistsHelper": "Albumul există deja",
"@newAlbumDialogAlbumAlreadyExistsHelper": {},
"mapAttributionOsmLiberty": "Plăci de la [OpenMapTiles](https://www.openmaptiles.org/), [CC BY](http://creativecommons.org/licenses/by/4.0) • Găzduit de [OSM Americana](https://tile.ourmap.us)",
- "@mapAttributionOsmLiberty": {}
+ "@mapAttributionOsmLiberty": {},
+ "mapStyleOpenTopoMap": "OpenTopoMap",
+ "@mapStyleOpenTopoMap": {},
+ "editEntryLocationDialogTimeShift": "Schimb de timp",
+ "@editEntryLocationDialogTimeShift": {},
+ "coordinateFormatDdm": "DDM",
+ "@coordinateFormatDdm": {},
+ "mapStyleOsmLiberty": "OSM Liberty",
+ "@mapStyleOsmLiberty": {},
+ "removeEntryMetadataDialogAll": "Toate",
+ "@removeEntryMetadataDialogAll": {}
}
diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb
index 6dc418908..e979a37f7 100644
--- a/lib/l10n/app_zh.arb
+++ b/lib/l10n/app_zh.arb
@@ -1409,7 +1409,7 @@
"@dynamicAlbumAlreadyExists": {},
"chipActionDecompose": "分割",
"@chipActionDecompose": {},
- "coordinateFormatDdm": "DDM",
+ "coordinateFormatDdm": "度分秒十进制坐标",
"@coordinateFormatDdm": {},
"editEntryLocationDialogImportGpx": "导入 GPX",
"@editEntryLocationDialogImportGpx": {},
diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb
index 562afc9a1..ff3175b41 100644
--- a/lib/l10n/app_zh_Hant.arb
+++ b/lib/l10n/app_zh_Hant.arb
@@ -1566,5 +1566,47 @@
"albumTierDynamic": "動態",
"@albumTierDynamic": {},
"chipActionRemove": "移除",
- "@chipActionRemove": {}
+ "@chipActionRemove": {},
+ "editEntryLocationDialogImportGpx": "匯入 GPX",
+ "@editEntryLocationDialogImportGpx": {},
+ "editEntryLocationDialogTimeShift": "時光平移",
+ "@editEntryLocationDialogTimeShift": {},
+ "videoActionShowPreviousFrame": "顯示前一幀",
+ "@videoActionShowPreviousFrame": {},
+ "videoActionShowNextFrame": "顯示下一幀",
+ "@videoActionShowNextFrame": {},
+ "mapAttributionOsmLiberty": "地圖由 [OpenMapTiles](https://www.openmaptiles.org/) 所提供,以 [CC BY](http://creativecommons.org/licenses/by/4.0) 授權 • 托管於 [OSM Americana](https://tile.ourmap.us)",
+ "@mapAttributionOsmLiberty": {},
+ "newDynamicAlbumDialogTitle": "新動態相簿",
+ "@newDynamicAlbumDialogTitle": {},
+ "sortOrderShortestFirst": "先短後長",
+ "@sortOrderShortestFirst": {},
+ "appExportDynamicAlbums": "動態相簿",
+ "@appExportDynamicAlbums": {},
+ "coordinateFormatDdm": "度分十進制",
+ "@coordinateFormatDdm": {},
+ "newAlbumDialogAlbumAlreadyExistsHelper": "相簿已經存在",
+ "@newAlbumDialogAlbumAlreadyExistsHelper": {},
+ "dynamicAlbumAlreadyExists": "動態相簿已經存在",
+ "@dynamicAlbumAlreadyExists": {},
+ "collectionActionAddDynamicAlbum": "新增動態相簿",
+ "@collectionActionAddDynamicAlbum": {},
+ "sortOrderLongestFirst": "先長後短",
+ "@sortOrderLongestFirst": {},
+ "setHomeCustom": "自定義",
+ "@setHomeCustom": {},
+ "removeEntryMetadataDialogAll": "全部",
+ "@removeEntryMetadataDialogAll": {},
+ "explorerActionSelectStorageVolume": "選擇儲存位置",
+ "@explorerActionSelectStorageVolume": {},
+ "selectStorageVolumeDialogTitle": "選擇儲存位置",
+ "@selectStorageVolumeDialogTitle": {},
+ "mapStyleOsmLiberty": "OSM Liberty",
+ "@mapStyleOsmLiberty": {},
+ "mapStyleOpenTopoMap": "OpenTopoMap",
+ "@mapStyleOpenTopoMap": {},
+ "mapAttributionOpenTopoMap": "[SRTM](https://www.earthdata.nasa.gov/sensors/srtm) | 地圖由 [OpenTopoMap](https://opentopomap.org/),以 [CC BY-SA](https://creativecommons.org/licenses/by-sa/3.0/) 授權",
+ "@mapAttributionOpenTopoMap": {},
+ "sortByDuration": "按時長",
+ "@sortByDuration": {}
}
diff --git a/lib/l10ngen/app_localizations_bg.dart b/lib/l10ngen/app_localizations_bg.dart
index 9713e3457..b57feb387 100644
--- a/lib/l10ngen/app_localizations_bg.dart
+++ b/lib/l10ngen/app_localizations_bg.dart
@@ -1771,10 +1771,10 @@ class AppLocalizationsBg extends AppLocalizations {
String get settingsThumbnailSectionTitle => 'Миниатюри';
@override
- String get settingsThumbnailOverlayTile => 'Наслагване';
+ String get settingsThumbnailOverlayTile => 'Повече информация';
@override
- String get settingsThumbnailOverlayPageTitle => 'Наслагване';
+ String get settingsThumbnailOverlayPageTitle => 'Повече информация';
@override
String get settingsThumbnailShowHdrIcon => 'Показване на HDR икона';
@@ -1861,10 +1861,10 @@ class AppLocalizationsBg extends AppLocalizations {
String get settingsViewerQuickActionEmpty => 'Без бутони';
@override
- String get settingsViewerOverlayTile => 'Наслагване';
+ String get settingsViewerOverlayTile => 'Повече информация';
@override
- String get settingsViewerOverlayPageTitle => 'Наслагване';
+ String get settingsViewerOverlayPageTitle => 'Повече информация';
@override
String get settingsViewerShowOverlayOnOpening => 'Показване при отваряне';
diff --git a/lib/l10ngen/app_localizations_ro.dart b/lib/l10ngen/app_localizations_ro.dart
index 06fb7d28b..4acbf8f15 100644
--- a/lib/l10ngen/app_localizations_ro.dart
+++ b/lib/l10ngen/app_localizations_ro.dart
@@ -991,7 +991,7 @@ class AppLocalizationsRo extends AppLocalizations {
String get editEntryLocationDialogLongitude => 'Longitudine';
@override
- String get editEntryLocationDialogTimeShift => 'Time shift';
+ String get editEntryLocationDialogTimeShift => 'Schimb de timp';
@override
String get locationPickerUseThisLocationButton => 'Utilizați această locație';
@@ -1003,7 +1003,7 @@ class AppLocalizationsRo extends AppLocalizations {
String get removeEntryMetadataDialogTitle => 'Eliminarea metadatelor';
@override
- String get removeEntryMetadataDialogAll => 'All';
+ String get removeEntryMetadataDialogAll => 'Toate';
@override
String get removeEntryMetadataDialogMore => 'Mai mult';
diff --git a/lib/l10ngen/app_localizations_zh.dart b/lib/l10ngen/app_localizations_zh.dart
index 9ffb0f4d2..be15a5e3c 100644
--- a/lib/l10ngen/app_localizations_zh.dart
+++ b/lib/l10ngen/app_localizations_zh.dart
@@ -505,7 +505,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get coordinateFormatDms => 'DMS';
@override
- String get coordinateFormatDdm => 'DDM';
+ String get coordinateFormatDdm => '度分秒十进制坐标';
@override
String get coordinateFormatDecimal => '十进制度';
@@ -2669,6 +2669,12 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get videoActionSkip10 => '前進 10 秒';
+ @override
+ String get videoActionShowPreviousFrame => '顯示前一幀';
+
+ @override
+ String get videoActionShowNextFrame => '顯示下一幀';
+
@override
String get videoActionSelectStreams => '選擇音軌';
@@ -2840,6 +2846,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get coordinateFormatDms => 'DMS';
+ @override
+ String get coordinateFormatDdm => '度分十進制';
+
@override
String get coordinateFormatDecimal => '十進制度數';
@@ -2893,6 +2902,12 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get mapStyleGoogleTerrain => 'Google 地圖 (地形)';
+ @override
+ String get mapStyleOsmLiberty => 'OSM Liberty';
+
+ @override
+ String get mapStyleOpenTopoMap => 'OpenTopoMap';
+
@override
String get mapStyleOsmHot => 'Humanitarian OSM';
@@ -3139,12 +3154,21 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get newAlbumDialogNameLabel => '相簿名稱';
+ @override
+ String get newAlbumDialogAlbumAlreadyExistsHelper => '相簿已經存在';
+
@override
String get newAlbumDialogNameLabelAlreadyExistsHelper => '目錄已存在';
@override
String get newAlbumDialogStorageLabel => '儲存空間:';
+ @override
+ String get newDynamicAlbumDialogTitle => '新動態相簿';
+
+ @override
+ String get dynamicAlbumAlreadyExists => '動態相簿已經存在';
+
@override
String get newVaultWarningDialogMessage => '保險庫中的項目僅供此應用使用,其他應用不可用。\n\n如果您卸載此應用程序或清除此應用程序數據,您將丟失所有這些項目。';
@@ -3296,12 +3320,18 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get editEntryLocationDialogChooseOnMap => '從地圖上選擇';
+ @override
+ String get editEntryLocationDialogImportGpx => '匯入 GPX';
+
@override
String get editEntryLocationDialogLatitude => '緯度';
@override
String get editEntryLocationDialogLongitude => '經度';
+ @override
+ String get editEntryLocationDialogTimeShift => '時光平移';
+
@override
String get locationPickerUseThisLocationButton => '使用此座標';
@@ -3311,6 +3341,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get removeEntryMetadataDialogTitle => '移除元資料';
+ @override
+ String get removeEntryMetadataDialogAll => '全部';
+
@override
String get removeEntryMetadataDialogMore => '更多';
@@ -3512,6 +3545,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get collectionActionHideTitleSearch => '隱藏標題過濾器';
+ @override
+ String get collectionActionAddDynamicAlbum => '新增動態相簿';
+
@override
String get collectionActionAddShortcut => '新增捷徑';
@@ -3741,6 +3777,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get sortByRating => '依評分';
+ @override
+ String get sortByDuration => '按時長';
+
@override
String get sortOrderNewestFirst => '由新至舊';
@@ -3765,6 +3804,12 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get sortOrderSmallestFirst => '由小到大';
+ @override
+ String get sortOrderShortestFirst => '先短後長';
+
+ @override
+ String get sortOrderLongestFirst => '先長後短';
+
@override
String get albumGroupTier => '依層級';
@@ -3849,6 +3894,12 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get explorerPageTitle => '檔案總管';
+ @override
+ String get explorerActionSelectStorageVolume => '選擇儲存位置';
+
+ @override
+ String get selectStorageVolumeDialogTitle => '選擇儲存位置';
+
@override
String get searchCollectionFieldHint => '搜尋收藏品';
@@ -3918,6 +3969,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get appExportCovers => '封面';
+ @override
+ String get appExportDynamicAlbums => '動態相簿';
+
@override
String get appExportFavourites => '我的最愛';
@@ -3933,6 +3987,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get settingsHomeDialogTitle => '主畫面';
+ @override
+ String get setHomeCustom => '自定義';
+
@override
String get settingsShowBottomNavigationBar => '顯示底部操作條';
@@ -4487,6 +4544,12 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get mapAttributionOsmData => '地圖資料由 © [OpenStreetMap](https://www.openstreetmap.org/copyright) 貢獻';
+ @override
+ String get mapAttributionOsmLiberty => '地圖由 [OpenMapTiles](https://www.openmaptiles.org/) 所提供,以 [CC BY](http://creativecommons.org/licenses/by/4.0) 授權 • 托管於 [OSM Americana](https://tile.ourmap.us)';
+
+ @override
+ String get mapAttributionOpenTopoMap => '[SRTM](https://www.earthdata.nasa.gov/sensors/srtm) | 地圖由 [OpenTopoMap](https://opentopomap.org/),以 [CC BY-SA](https://creativecommons.org/licenses/by-sa/3.0/) 授權';
+
@override
String get mapAttributionOsmHot => '繪製於 [HOT](https://www.hotosm.org/) • 主辦方 [OSM France](https://openstreetmap.fr/)';
diff --git a/lib/model/app/contributors.dart b/lib/model/app/contributors.dart
index 850003edb..53ee62d2c 100644
--- a/lib/model/app/contributors.dart
+++ b/lib/model/app/contributors.dart
@@ -129,6 +129,7 @@ class Contributors {
Contributor('Josep M. Ferrer', 'txemaq@gmail.com'),
Contributor('pitroig', 'ona@riseup.net'),
Contributor('Rubén Castiñeiras Lorenzo', 'rcasl@outlook.com'),
+ Contributor('hanyang cheng', 'cinxiafortis@tutanota.de'),
// Contributor('Femini', 'nizamismidov4@gmail.com'), // Azerbaijani
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
diff --git a/lib/model/db/db_extension.dart b/lib/model/db/db_extension.dart
index 385fff5e0..a0d2a6659 100644
--- a/lib/model/db/db_extension.dart
+++ b/lib/model/db/db_extension.dart
@@ -1,15 +1,13 @@
import 'package:sqflite/sqflite.dart';
extension ExtraDatabase on Database {
- // check table existence
- // proper way is to select from `sqlite_master` but this meta table may be missing on some devices
- // so we rely on failure check instead
- bool tableExists(String table) {
- try {
- query(table, limit: 1);
- return true;
- } catch (error) {
- return false;
- }
+ // check table existence via `sqlite_master`
+ // `sqlite_schema` is the alias used in SQLite documentation,
+ // but it was introduced in SQLite v3.33.0 and it is unavailable on Android API < 34,
+ // and the historical alias `sqlite_master` is still supported.
+ // cf https://www.sqlite.org/faq.html#q7
+ Future tableExists(String table) async {
+ final results = await query('sqlite_master', where: 'type = ? AND name = ?', whereArgs: ['table', table]);
+ return results.isNotEmpty;
}
}
diff --git a/lib/model/db/db_sqflite_upgrade.dart b/lib/model/db/db_sqflite_upgrade.dart
index 1442aa13c..bf7e8b122 100644
--- a/lib/model/db/db_sqflite_upgrade.dart
+++ b/lib/model/db/db_sqflite_upgrade.dart
@@ -62,7 +62,7 @@ class LocalMediaDbUpgrader {
static Future _sanitize(Database db) async {
// ensure all tables exist
await Future.forEach(SqfliteLocalMediaDbSchema.allTables, (table) async {
- if (!db.tableExists(table)) {
+ if (!(await db.tableExists(table))) {
await SqfliteLocalMediaDbSchema.createTable(db, table);
}
});
@@ -464,7 +464,7 @@ class LocalMediaDbUpgrader {
static Future _upgradeFrom13(Database db) async {
debugPrint('upgrading DB from v13');
- if (db.tableExists(entryTable)) {
+ if (await db.tableExists(entryTable)) {
// rename column 'dateModifiedSecs' to 'dateModifiedMillis'
const newEntryTable = '${entryTable}TEMP';
await db.execute('CREATE TABLE $newEntryTable ('
@@ -500,7 +500,7 @@ class LocalMediaDbUpgrader {
// so we clear rebuildable tables
final tables = [dateTakenTable, metadataTable, addressTable, trashTable, videoPlaybackTable];
await Future.forEach(tables, (table) async {
- if (db.tableExists(table)) {
+ if (await db.tableExists(table)) {
await db.delete(table, where: '1');
}
});
diff --git a/lib/theme/themes.dart b/lib/theme/themes.dart
index b77ea96f8..252017dc2 100644
--- a/lib/theme/themes.dart
+++ b/lib/theme/themes.dart
@@ -58,7 +58,6 @@ class Themes {
cardColor: _schemeCardLayer(colors),
colorScheme: colors,
dividerColor: colors.outlineVariant,
- indicatorColor: colors.primary,
scaffoldBackgroundColor: _schemeFirstLayer(colors),
// TYPOGRAPHY & ICONOGRAPHY
typography: _typography,
@@ -75,6 +74,7 @@ class Themes {
),
radioTheme: _radioTheme(colors),
sliderTheme: _sliderTheme(colors),
+ tabBarTheme: TabBarThemeData(indicatorColor: colors.primary),
tooltipTheme: _tooltipTheme,
);
}
@@ -183,7 +183,7 @@ class Themes {
titleTextStyle: _titleTextStyle.copyWith(color: _lightTitleColor),
systemOverlayStyle: deviceInitialized ? AvesApp.systemUIStyleForBrightness(colors.brightness, _schemeFirstLayer(colors)) : null,
),
- dialogTheme: DialogTheme(
+ dialogTheme: DialogThemeData(
backgroundColor: _schemeSecondLayer(colors),
titleTextStyle: _titleTextStyle.copyWith(color: _lightTitleColor),
),
@@ -236,7 +236,7 @@ class Themes {
titleTextStyle: _titleTextStyle.copyWith(color: _darkTitleColor),
systemOverlayStyle: deviceInitialized ? AvesApp.systemUIStyleForBrightness(colors.brightness, _schemeFirstLayer(colors)) : null,
),
- dialogTheme: DialogTheme(
+ dialogTheme: DialogThemeData(
backgroundColor: _schemeSecondLayer(colors),
titleTextStyle: _titleTextStyle.copyWith(color: _darkTitleColor),
),
diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart
index f124ce6ae..9c0b74376 100644
--- a/lib/widgets/debug/app_debug_page.dart
+++ b/lib/widgets/debug/app_debug_page.dart
@@ -18,6 +18,7 @@ import 'package:aves/widgets/debug/capabilities.dart';
import 'package:aves/widgets/debug/colors.dart';
import 'package:aves/widgets/debug/database.dart';
import 'package:aves/widgets/debug/general.dart';
+import 'package:aves/widgets/debug/leaking.dart';
import 'package:aves/widgets/debug/media_store_scan_dialog.dart';
import 'package:aves/widgets/debug/os_apps.dart';
import 'package:aves/widgets/debug/os_codecs.dart';
@@ -73,6 +74,7 @@ class AppDebugPage extends StatelessWidget {
padding: const EdgeInsets.all(8),
children: const [
DebugGeneralSection(),
+ DebugLeakingSection(),
DebugCacheSection(),
DebugCapabilitiesSection(),
DebugColorSection(),
diff --git a/lib/widgets/debug/general.dart b/lib/widgets/debug/general.dart
index 985dc20f3..458c9fcf1 100644
--- a/lib/widgets/debug/general.dart
+++ b/lib/widgets/debug/general.dart
@@ -1,14 +1,12 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/services/analysis_service.dart';
+import 'package:aves/services/common/service_policy.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
-import 'package:aves/widgets/debug/overlay.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/viewer/info/common.dart';
-import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
-import 'package:leak_tracker/leak_tracker.dart';
import 'package:provider/provider.dart';
class DebugGeneralSection extends StatefulWidget {
@@ -31,6 +29,7 @@ class _DebugGeneralSectionState extends State with Automati
final withGps = catalogued.where((entry) => entry.hasGps);
final withAddress = withGps.where((entry) => entry.hasAddress);
final withFineAddress = withGps.where((entry) => entry.hasFineAddress);
+
return AvesExpansionTile(
title: 'General',
children: [
@@ -55,7 +54,7 @@ class _DebugGeneralSectionState extends State with Automati
_taskQueueOverlayEntry = null;
if (v) {
_taskQueueOverlayEntry = OverlayEntry(
- builder: (context) => const DebugTaskQueueOverlay(),
+ builder: (context) => const _TaskQueueOverlay(),
);
Overlay.of(context).insert(_taskQueueOverlayEntry!);
}
@@ -68,46 +67,6 @@ class _DebugGeneralSectionState extends State with Automati
onChanged: (v) => settings.debugShowViewerTiles = v,
title: 'Show viewer tiles',
),
- ElevatedButton(
- onPressed: () => LeakTracking.collectLeaks().then((leaks) {
- const config = LeakDiagnosticConfig(
- collectRetainingPathForNotGCed: true,
- collectStackTraceOnStart: true,
- collectStackTraceOnDisposal: true,
- );
- LeakTracking.phase = const PhaseSettings(
- leakDiagnosticConfig: config,
- );
- debugPrint('Setup leak tracking phase with config=$config');
- }),
- child: const Text('Setup leak tracking phase'),
- ),
- ElevatedButton(
- onPressed: () => LeakTracking.collectLeaks().then((leaks) {
- leaks.byType.forEach((type, reports) {
- // ignore `notGCed` and `gcedLate` for now
- if (type != LeakType.notDisposed) return;
-
- debugPrint('* leak type=$type');
- groupBy(reports, (report) => report.type).forEach((reportType, typedReports) {
- debugPrint(' * report type=$reportType');
- groupBy(typedReports, (report) => report.trackedClass).forEach((trackedClass, classedReports) {
- debugPrint(' trackedClass=$trackedClass reports=${classedReports.length}');
- classedReports.forEach((report) {
- final phase = report.phase;
- final retainingPath = report.retainingPath;
- final detailedPath = report.detailedPath;
- final context = report.context;
- if (phase != null || retainingPath != null || detailedPath != null || context != null) {
- debugPrint(' phase=$phase retainingPath=$retainingPath detailedPath=$detailedPath context=$context');
- }
- });
- });
- });
- });
- }),
- child: const Text('Collect leaks'),
- ),
ElevatedButton(
onPressed: () => AnalysisService.startService(force: false),
child: const Text('Start analysis service'),
@@ -133,3 +92,45 @@ class _DebugGeneralSectionState extends State with Automati
@override
bool get wantKeepAlive => true;
}
+
+class _TaskQueueOverlay extends StatelessWidget {
+ const _TaskQueueOverlay();
+
+ @override
+ Widget build(BuildContext context) {
+ return IgnorePointer(
+ child: DefaultTextStyle(
+ style: const TextStyle(),
+ child: Align(
+ alignment: AlignmentDirectional.bottomStart,
+ child: SafeArea(
+ child: Container(
+ color: Colors.indigo.shade900.withAlpha(0xCC),
+ padding: const EdgeInsets.all(8),
+ child: StreamBuilder(
+ stream: servicePolicy.queueStream,
+ builder: (context, snapshot) {
+ if (snapshot.hasError) return const SizedBox();
+ final queuedEntries = >[];
+ if (snapshot.hasData) {
+ final state = snapshot.data!;
+ queuedEntries.add(MapEntry('run', state.runningCount));
+ queuedEntries.add(MapEntry('paused', state.pausedCount));
+ queuedEntries.addAll(state.queueByPriority.entries.map((kv) => MapEntry(kv.key.toString(), kv.value)));
+ }
+ queuedEntries.sort((a, b) => a.key.compareTo(b.key));
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Text(queuedEntries.map((kv) => '${kv.key}: ${kv.value}').join(', ')),
+ ],
+ );
+ }),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/debug/leaking.dart b/lib/widgets/debug/leaking.dart
new file mode 100644
index 000000000..7a9780855
--- /dev/null
+++ b/lib/widgets/debug/leaking.dart
@@ -0,0 +1,180 @@
+import 'dart:io';
+
+import 'package:aves/ref/locales.dart';
+import 'package:aves/utils/file_utils.dart';
+import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
+import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:leak_tracker/leak_tracker.dart';
+
+class DebugLeakingSection extends StatefulWidget {
+ const DebugLeakingSection({super.key});
+
+ @override
+ State createState() => _DebugLeakingSectionState();
+}
+
+class _DebugLeakingSectionState extends State with AutomaticKeepAliveClientMixin {
+ static OverlayEntry? _collectorOverlayEntry;
+
+ static const _leakIgnoreConfig = IgnoredLeaks(
+ experimentalNotGCed: IgnoredLeaksSet(),
+ notDisposed: IgnoredLeaksSet(),
+ );
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+
+ return AvesExpansionTile(
+ title: 'Leaking',
+ children: [
+ SwitchListTile(
+ value: _collectorOverlayEntry != null,
+ onChanged: (v) {
+ _collectorOverlayEntry
+ ?..remove()
+ ..dispose();
+ _collectorOverlayEntry = null;
+ if (v) {
+ _collectorOverlayEntry = OverlayEntry(
+ builder: (context) => const _CollectorOverlay(),
+ );
+ Overlay.of(context).insert(_collectorOverlayEntry!);
+ }
+ setState(() {});
+ },
+ title: const Text('Show leak report overlay'),
+ ),
+ ElevatedButton(
+ onPressed: () => LeakTracking.collectLeaks().then((leaks) {
+ LeakTracking.phase = const PhaseSettings(
+ ignoredLeaks: _leakIgnoreConfig,
+ leakDiagnosticConfig: LeakDiagnosticConfig(
+ collectRetainingPathForNotGCed: true,
+ collectStackTraceOnStart: true,
+ collectStackTraceOnDisposal: true,
+ ),
+ );
+ }),
+ child: const Text('Track leaks with stacks'),
+ ),
+ ElevatedButton(
+ onPressed: () => LeakTracking.collectLeaks().then((leaks) {
+ LeakTracking.phase = const PhaseSettings(
+ ignoredLeaks: _leakIgnoreConfig,
+ leakDiagnosticConfig: LeakDiagnosticConfig(
+ collectRetainingPathForNotGCed: true,
+ collectStackTraceOnStart: false,
+ collectStackTraceOnDisposal: false,
+ ),
+ );
+ }),
+ child: const Text('Track leaks without stacks'),
+ ),
+ ],
+ );
+ }
+
+ @override
+ bool get wantKeepAlive => true;
+}
+
+class _CollectorOverlay extends StatefulWidget {
+ const _CollectorOverlay();
+
+ @override
+ State<_CollectorOverlay> createState() => _CollectorOverlayState();
+}
+
+class _CollectorOverlayState extends State<_CollectorOverlay> {
+ AlignmentGeometry _alignment = AlignmentDirectional.bottomStart;
+
+ @override
+ Widget build(BuildContext context) {
+ return DefaultTextStyle(
+ style: const TextStyle(),
+ child: Align(
+ alignment: _alignment,
+ child: SafeArea(
+ child: Container(
+ color: Colors.indigo.shade900.withAlpha(0xCC),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Wrap(
+ crossAxisAlignment: WrapCrossAlignment.center,
+ children: [
+ IconButton(
+ onPressed: () => setState(() => _alignment = _alignment == AlignmentDirectional.bottomStart ? AlignmentDirectional.topStart : AlignmentDirectional.bottomStart),
+ icon: Icon(_alignment == AlignmentDirectional.bottomStart ? Icons.vertical_align_top_outlined : Icons.vertical_align_bottom_outlined),
+ ),
+ ...LeakType.values.map((type) {
+ return OutlinedButton(
+ style: ButtonStyle(
+ padding: WidgetStateProperty.all(const EdgeInsets.all(6)),
+ minimumSize: WidgetStateProperty.all(Size.zero),
+ ),
+ onPressed: () => LeakTracking.collectLeaks().then((leaks) {
+ final reports = leaks.byType[type] ?? [];
+ _printLeaks(type, reports);
+ }),
+ child: Text(type.name),
+ );
+ }),
+ ],
+ ),
+ Wrap(
+ crossAxisAlignment: WrapCrossAlignment.center,
+ children: [
+ StreamBuilder(
+ stream: Stream.periodic(const Duration(seconds: 1)),
+ builder: (context, snapshot) {
+ final currentRss = formatFileSize(asciiLocale, ProcessInfo.currentRss);
+ final maxRss = formatFileSize(asciiLocale, ProcessInfo.maxRss);
+ return Text('RSS: $currentRss / $maxRss');
+ },
+ ),
+ ],
+ ),
+ Wrap(
+ crossAxisAlignment: WrapCrossAlignment.center,
+ children: [
+ StreamBuilder(
+ stream: Stream.periodic(const Duration(seconds: 1)),
+ builder: (context, snapshot) {
+ final currentImageCache = formatFileSize(asciiLocale, imageCache.currentSizeBytes);
+ final maxImageCache = formatFileSize(asciiLocale, imageCache.maximumSizeBytes);
+ return Text('imageCache: $currentImageCache / $maxImageCache');
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ void _printLeaks(LeakType type, List reports) {
+ debugPrint('* leak type=$type, ${reports.length} reports');
+ groupBy(reports, (report) => report.type).forEach((reportType, typedReports) {
+ debugPrint(' * report type=$reportType');
+ groupBy(typedReports, (report) => report.trackedClass).forEach((trackedClass, classedReports) {
+ debugPrint(' trackedClass=$trackedClass reports=${classedReports.length}');
+ classedReports.forEach((report) {
+ final phase = report.phase;
+ final retainingPath = report.retainingPath;
+ final detailedPath = report.detailedPath;
+ final context = report.context;
+ if (phase != null || retainingPath != null || detailedPath != null || context != null) {
+ debugPrint(' phase=$phase retainingPath=$retainingPath detailedPath=$detailedPath context=$context');
+ }
+ });
+ });
+ });
+ }
+}
diff --git a/lib/widgets/debug/overlay.dart b/lib/widgets/debug/overlay.dart
deleted file mode 100644
index 6011989a5..000000000
--- a/lib/widgets/debug/overlay.dart
+++ /dev/null
@@ -1,44 +0,0 @@
-import 'package:aves/services/common/service_policy.dart';
-import 'package:flutter/material.dart';
-
-class DebugTaskQueueOverlay extends StatelessWidget {
- const DebugTaskQueueOverlay({super.key});
-
- @override
- Widget build(BuildContext context) {
- return IgnorePointer(
- child: DefaultTextStyle(
- style: const TextStyle(),
- child: Align(
- alignment: AlignmentDirectional.bottomStart,
- child: SafeArea(
- child: Container(
- color: Colors.indigo.shade900.withAlpha(0xCC),
- padding: const EdgeInsets.all(8),
- child: StreamBuilder(
- stream: servicePolicy.queueStream,
- builder: (context, snapshot) {
- if (snapshot.hasError) return const SizedBox();
- final queuedEntries = >[];
- if (snapshot.hasData) {
- final state = snapshot.data!;
- queuedEntries.add(MapEntry('run', state.runningCount));
- queuedEntries.add(MapEntry('paused', state.pausedCount));
- queuedEntries.addAll(state.queueByPriority.entries.map((kv) => MapEntry(kv.key.toString(), kv.value)));
- }
- queuedEntries.sort((a, b) => a.key.compareTo(b.key));
- return Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Text(queuedEntries.map((kv) => '${kv.key}: ${kv.value}').join(', ')),
- ],
- );
- }),
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart
index cea7ec997..fbdbef1d1 100644
--- a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart
+++ b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart
@@ -67,7 +67,7 @@ class _EditEntryLocationDialogState extends State with
final TextEditingController _latitudeController = TextEditingController(), _longitudeController = TextEditingController();
final ValueNotifier _isValidNotifier = ValueNotifier(false);
- NumberFormat get coordinateFormatter => NumberFormat('0.000000', context.locale);
+ late NumberFormat coordinateFormatter;
static const _minTimeToGpxPoint = Duration(hours: 1);
@override
@@ -75,22 +75,10 @@ class _EditEntryLocationDialogState extends State with
super.initState();
final entries = widget.entries;
mainEntry = entries.firstWhereOrNull((entry) => entry.hasGps) ?? entries.first;
- _initMapCoordinates();
- _initCopyItem();
- _initCustom();
- AvesApp.intentEventBus.on().listen((event) => _setCustomLocation(event.location));
- }
-
- void _initMapCoordinates() {
_mapCoordinates = mainEntry.latLng;
- }
-
- void _initCopyItem() {
_copyItemSource = mainEntry;
- }
-
- void _initCustom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
+ coordinateFormatter = NumberFormat('0.000000', context.locale);
final latLng = mainEntry.latLng;
if (latLng != null) {
_latitudeController.text = coordinateFormatter.format(latLng.latitude);
@@ -101,6 +89,7 @@ class _EditEntryLocationDialogState extends State with
}
setState(_validate);
});
+ _subscriptions.add(AvesApp.intentEventBus.on().listen((event) => _setCustomLocation(event.location)));
}
@override
diff --git a/lib/widgets/home/home_page.dart b/lib/widgets/home/home_page.dart
index 8ea841bf0..cc4cd1875 100644
--- a/lib/widgets/home/home_page.dart
+++ b/lib/widgets/home/home_page.dart
@@ -274,7 +274,7 @@ class _HomePageState extends State {
));
} catch (error, stack) {
debugPrint('failed to setup app with error=$error\n$stack');
- _setupError = (error, stack);
+ setState(() => _setupError = (error, stack));
}
}
diff --git a/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart b/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart
index 9aa0329fa..04277a7a8 100644
--- a/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart
+++ b/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart
@@ -1,5 +1,6 @@
import 'package:aves_magnifier/src/controller/controller_delegate.dart';
import 'package:equatable/equatable.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
mixin EdgeHitDetector on AvesMagnifierControllerDelegate {
@@ -15,7 +16,7 @@ mixin EdgeHitDetector on AvesMagnifierControllerDelegate {
final x = -position.dx;
final range = _boundaries.getXEdges(scale: _scale);
- return EdgeHit(x <= range.min, x >= range.max);
+ return EdgeHit(x <= range.min + precisionErrorTolerance, x >= range.max - precisionErrorTolerance);
}
EdgeHit getYEdgeHit() {
@@ -25,7 +26,7 @@ mixin EdgeHitDetector on AvesMagnifierControllerDelegate {
final y = -position.dy;
final range = _boundaries.getYEdges(scale: _scale);
- return EdgeHit(y <= range.min, y >= range.max);
+ return EdgeHit(y <= range.min + precisionErrorTolerance, y >= range.max - precisionErrorTolerance);
}
bool shouldMoveX(Offset move, bool canFling) {
diff --git a/plugins/aves_platform_meta/android/.gitignore b/plugins/aves_platform_meta/android/.gitignore
index be3943c96..82677b89f 100644
--- a/plugins/aves_platform_meta/android/.gitignore
+++ b/plugins/aves_platform_meta/android/.gitignore
@@ -6,6 +6,7 @@ gradle-wrapper.jar
/local.properties
GeneratedPluginRegistrant.java
.cxx/
+.kotlin/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
diff --git a/plugins/aves_screen_state/android/.gitignore b/plugins/aves_screen_state/android/.gitignore
index be3943c96..82677b89f 100644
--- a/plugins/aves_screen_state/android/.gitignore
+++ b/plugins/aves_screen_state/android/.gitignore
@@ -6,6 +6,7 @@ gradle-wrapper.jar
/local.properties
GeneratedPluginRegistrant.java
.cxx/
+.kotlin/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
diff --git a/pubspec.yaml b/pubspec.yaml
index 4cc3c3102..28024ff74 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,7 +7,7 @@ repository: https://github.com/deckerst/aves
# - play changelog: /whatsnew/whatsnew-en-US
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
-version: 1.12.7+147
+version: 1.12.8+148
publish_to: none
environment:
diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US
index e0b6e4678..3f381a910 100644
--- a/whatsnew/whatsnew-en-US
+++ b/whatsnew/whatsnew-en-US
@@ -1,4 +1,4 @@
-In v1.12.7:
+In v1.12.8:
- play more kinds of motion photos
- enjoy the app in Galician
Full changelog available on GitHub
\ No newline at end of file