Merge branch 'develop'
This commit is contained in:
commit
30f96334eb
31 changed files with 434 additions and 144 deletions
4
.github/workflows/quality-check.yml
vendored
4
.github/workflows/quality-check.yml
vendored
|
@ -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}}"
|
||||
|
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -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
|
||||
|
||||
|
|
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
|
@ -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
|
||||
|
|
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## <a id="unreleased"></a>[Unreleased]
|
||||
|
||||
## <a id="v1.12.8"></a>[v1.12.8] - 2025-03-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- swiping images for some combinations of screen size, device pixel ratio, and image size
|
||||
|
||||
## <a id="v1.12.7"></a>[v1.12.7] - 2025-03-16
|
||||
|
||||
### Added
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
<string name="search_shortcut_short_label">搜尋</string>
|
||||
<string name="analysis_channel_name">媒體掃描</string>
|
||||
<string name="analysis_notification_action_stop">停止</string>
|
||||
</resources>
|
||||
<string name="map_shortcut_short_label">地圖</string>
|
||||
</resources>
|
||||
|
|
|
@ -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
|
4
fastlane/metadata/android/en-US/changelogs/148.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/148.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
In v1.12.8:
|
||||
- play more kinds of motion photos
|
||||
- enjoy the app in Galician
|
||||
Full changelog available on GitHub
|
4
fastlane/metadata/android/en-US/changelogs/14801.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/14801.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
In v1.12.8:
|
||||
- play more kinds of motion photos
|
||||
- enjoy the app in Galician
|
||||
Full changelog available on GitHub
|
|
@ -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": {},
|
||||
|
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
@ -1409,7 +1409,7 @@
|
|||
"@dynamicAlbumAlreadyExists": {},
|
||||
"chipActionDecompose": "分割",
|
||||
"@chipActionDecompose": {},
|
||||
"coordinateFormatDdm": "DDM",
|
||||
"coordinateFormatDdm": "度分秒十进制坐标",
|
||||
"@coordinateFormatDdm": {},
|
||||
"editEntryLocationDialogImportGpx": "导入 GPX",
|
||||
"@editEntryLocationDialogImportGpx": {},
|
||||
|
|
|
@ -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": {}
|
||||
}
|
||||
|
|
|
@ -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 => 'Показване при отваряне';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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/)';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<bool> tableExists(String table) async {
|
||||
final results = await query('sqlite_master', where: 'type = ? AND name = ?', whereArgs: ['table', table]);
|
||||
return results.isNotEmpty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class LocalMediaDbUpgrader {
|
|||
static Future<void> _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<void> _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');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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<DebugGeneralSection> 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<DebugGeneralSection> 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<DebugGeneralSection> 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<DebugGeneralSection> 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<QueueState>(
|
||||
stream: servicePolicy.queueStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) return const SizedBox();
|
||||
final queuedEntries = <MapEntry<dynamic, int>>[];
|
||||
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(', ')),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
180
lib/widgets/debug/leaking.dart
Normal file
180
lib/widgets/debug/leaking.dart
Normal file
|
@ -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<DebugLeakingSection> createState() => _DebugLeakingSectionState();
|
||||
}
|
||||
|
||||
class _DebugLeakingSectionState extends State<DebugLeakingSection> 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<LeakReport> 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');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<QueueState>(
|
||||
stream: servicePolicy.queueStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) return const SizedBox();
|
||||
final queuedEntries = <MapEntry<dynamic, int>>[];
|
||||
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(', ')),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> with
|
|||
final TextEditingController _latitudeController = TextEditingController(), _longitudeController = TextEditingController();
|
||||
final ValueNotifier<bool> _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<EditEntryLocationDialog> with
|
|||
super.initState();
|
||||
final entries = widget.entries;
|
||||
mainEntry = entries.firstWhereOrNull((entry) => entry.hasGps) ?? entries.first;
|
||||
_initMapCoordinates();
|
||||
_initCopyItem();
|
||||
_initCustom();
|
||||
AvesApp.intentEventBus.on<LocationReceivedEvent>().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<EditEntryLocationDialog> with
|
|||
}
|
||||
setState(_validate);
|
||||
});
|
||||
_subscriptions.add(AvesApp.intentEventBus.on<LocationReceivedEvent>().listen((event) => _setCustomLocation(event.location)));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -274,7 +274,7 @@ class _HomePageState extends State<HomePage> {
|
|||
));
|
||||
} catch (error, stack) {
|
||||
debugPrint('failed to setup app with error=$error\n$stack');
|
||||
_setupError = (error, stack);
|
||||
setState(() => _setupError = (error, stack));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
1
plugins/aves_screen_state/android/.gitignore
vendored
1
plugins/aves_screen_state/android/.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue