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