l10n
This commit is contained in:
parent
a5c3971303
commit
a47d82ebfc
122 changed files with 1955 additions and 905 deletions
4
android/app/src/debug/res/values-ko/strings.xml
Normal file
4
android/app/src/debug/res/values-ko/strings.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<resources>
|
||||
<string name="app_name">아베스 [Debug]</string>
|
||||
</resources>
|
6
android/app/src/main/res/values-ko/strings.xml
Normal file
6
android/app/src/main/res/values-ko/strings.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<resources>
|
||||
<string name="app_name">아베스</string>
|
||||
<string name="search_shortcut_short_label">검색</string>
|
||||
<string name="videos_shortcut_short_label">동영상</string>
|
||||
</resources>
|
4
android/app/src/profile/res/values-ko/strings.xml
Normal file
4
android/app/src/profile/res/values-ko/strings.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<resources>
|
||||
<string name="app_name">아베스 [Profile]</string>
|
||||
</resources>
|
8
l10n.yaml
Normal file
8
l10n.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# cf guide: http://flutter.dev/go/i18n-user-guide
|
||||
|
||||
# use defaults to:
|
||||
# - parse ARB files from `lib/l10n`
|
||||
# - generate class `AppLocalizations` in `app_localizations.dart`
|
||||
|
||||
preferred-supported-locales:
|
||||
- en
|
659
lib/l10n/app_en.arb
Normal file
659
lib/l10n/app_en.arb
Normal file
|
@ -0,0 +1,659 @@
|
|||
{
|
||||
"appName": "Aves",
|
||||
"@appName": {},
|
||||
"welcomeMessage": "Welcome to Aves",
|
||||
"@welcomeMessage": {},
|
||||
"welcomeAnalyticsToggle": "Allow anonymous analytics and crash reporting (optional)",
|
||||
"@welcomeAnalyticsToggle": {},
|
||||
"welcomeTermsToggle": "I agree to the terms and conditions",
|
||||
"@welcomeTermsToggle": {},
|
||||
|
||||
"applyButtonLabel": "APPLY",
|
||||
"@applyButtonLabel": {},
|
||||
"deleteButtonLabel": "DELETE",
|
||||
"@deleteButtonLabel": {},
|
||||
"hideButtonLabel": "HIDE",
|
||||
"@hideButtonLabel": {},
|
||||
"continueButtonLabel": "CONTINUE",
|
||||
"@continueButtonLabel": {},
|
||||
"clearTooltip": "Clear",
|
||||
"@clearTooltip": {},
|
||||
"previousTooltip": "Previous",
|
||||
"@previousTooltip": {},
|
||||
"nextTooltip": "Next",
|
||||
"@nextTooltip": {},
|
||||
|
||||
"doubleBackExitMessage": "Tap “back” again to exit.",
|
||||
"@doubleBackExitMessage": {},
|
||||
|
||||
"sourceStateLoading": "Loading",
|
||||
"@sourceStateLoading": {},
|
||||
"sourceStateCataloguing": "Cataloguing",
|
||||
"@sourceStateCataloguing": {},
|
||||
"sourceStateLocating": "Locating",
|
||||
"@sourceStateLocating": {},
|
||||
|
||||
"chipActionDelete": "Delete",
|
||||
"@chipActionDelete": {},
|
||||
"chipActionGoToAlbumPage": "Show in Albums",
|
||||
"@chipActionGoToAlbumPage": {},
|
||||
"chipActionGoToCountryPage": "Show in Countries",
|
||||
"@chipActionGoToCountryPage": {},
|
||||
"chipActionGoToTagPage": "Show in Tags",
|
||||
"@chipActionGoToTagPage": {},
|
||||
"chipActionHide": "Hide",
|
||||
"@chipActionHide": {},
|
||||
"chipActionPin": "Pin to top",
|
||||
"@chipActionPin": {},
|
||||
"chipActionUnpin": "Unpin from top",
|
||||
"@chipActionUnpin": {},
|
||||
"chipActionRename": "Rename",
|
||||
"@chipActionRename": {},
|
||||
|
||||
"entryActionDelete": "Delete",
|
||||
"@entryActionDelete": {},
|
||||
"entryActionExport": "Export",
|
||||
"@entryActionExport": {},
|
||||
"entryActionInfo": "Info",
|
||||
"@entryActionInfo": {},
|
||||
"entryActionRename": "Rename",
|
||||
"@entryActionRename": {},
|
||||
"entryActionRotateCCW": "Rotate counterclockwise",
|
||||
"@entryActionRotateCCW": {},
|
||||
"entryActionRotateCW": "Rotate clockwise",
|
||||
"@entryActionRotateCW": {},
|
||||
"entryActionFlip": "Flip horizontally",
|
||||
"@entryActionFlip": {},
|
||||
"entryActionPrint": "Print",
|
||||
"@entryActionPrint": {},
|
||||
"entryActionShare": "Share",
|
||||
"@entryActionShare": {},
|
||||
"entryActionViewSource": "View source",
|
||||
"@entryActionViewSource": {},
|
||||
"entryActionEdit": "Edit with…",
|
||||
"@entryActionEdit": {},
|
||||
"entryActionOpen": "Open with…",
|
||||
"@entryActionOpen": {},
|
||||
"entryActionSetAs": "Set as…",
|
||||
"@entryActionSetAs": {},
|
||||
"entryActionOpenMap": "Show on map…",
|
||||
"@entryActionOpenMap": {},
|
||||
"entryActionAddFavourite": "Add to favourites",
|
||||
"@entryActionAddFavourite": {},
|
||||
"entryActionRemoveFavourite": "Remove from favourites",
|
||||
"@entryActionRemoveFavourite": {},
|
||||
|
||||
"filterFavouriteLabel": "Favourite",
|
||||
"@filterFavouriteLabel": {},
|
||||
"filterLocationEmptyLabel": "Unlocated",
|
||||
"@filterLocationEmptyLabel": {},
|
||||
"filterTagEmptyLabel": "Untagged",
|
||||
"@filterTagEmptyLabel": {},
|
||||
"filterTypeAnimatedLabel": "Animated",
|
||||
"@filterTypeAnimatedLabel": {},
|
||||
"filterTypePanoramaLabel": "Panorama",
|
||||
"@filterTypePanoramaLabel": {},
|
||||
"filterTypeSphericalVideoLabel": "360° Video",
|
||||
"@filterTypeSphericalVideoLabel": {},
|
||||
"filterTypeGeotiffLabel": "GeoTIFF",
|
||||
"@filterTypeGeotiffLabel": {},
|
||||
"filterMimeImageLabel": "Image",
|
||||
"@filterMimeImageLabel": {},
|
||||
"filterMimeVideoLabel": "Video",
|
||||
"@filterMimeVideoLabel": {},
|
||||
|
||||
"coordinateFormatDms": "DMS",
|
||||
"@coordinateFormatDms": {},
|
||||
"coordinateFormatDecimal": "Decimal degrees",
|
||||
"@coordinateFormatDecimal": {},
|
||||
|
||||
"mapStyleGoogleNormal": "Google Maps",
|
||||
"@mapStyleGoogleNormal": {},
|
||||
"mapStyleGoogleHybrid": "Google Maps (Hybrid)",
|
||||
"@mapStyleGoogleHybrid": {},
|
||||
"mapStyleGoogleTerrain": "Google Maps (Terrain)",
|
||||
"@mapStyleGoogleTerrain": {},
|
||||
"mapStyleOsmHot": "Humanitarian OSM",
|
||||
"@mapStyleOsmHot": {},
|
||||
"mapStyleStamenToner": "Stamen Toner",
|
||||
"@mapStyleStamenToner": {},
|
||||
"mapStyleStamenWatercolor": "Stamen Watercolor",
|
||||
"@mapStyleStamenWatercolor": {},
|
||||
|
||||
"keepScreenOnNever": "Never",
|
||||
"@keepScreenOnNever": {},
|
||||
"keepScreenOnViewerOnly": "Viewer page only",
|
||||
"@keepScreenOnViewerOnly": {},
|
||||
"keepScreenOnAlways": "Always",
|
||||
"@keepScreenOnAlways": {},
|
||||
|
||||
"albumTierPinned": "Pinned",
|
||||
"@albumTierPinned": {},
|
||||
"albumTierSpecial": "Common",
|
||||
"@albumTierSpecial": {},
|
||||
"albumTierApps": "Apps",
|
||||
"@albumTierApps": {},
|
||||
"albumTierRegular": "Others",
|
||||
"@albumTierRegular": {},
|
||||
|
||||
"storageVolumeDescriptionFallbackPrimary": "Internal storage",
|
||||
"@storageVolumeDescriptionFallbackPrimary": {},
|
||||
"storageVolumeDescriptionFallbackNonPrimary": "SD card",
|
||||
"@storageVolumeDescriptionFallbackNonPrimary": {},
|
||||
"rootDirectoryDescription": "root directory",
|
||||
"@rootDirectoryDescription": {},
|
||||
"otherDirectoryDescription": "“{name}” directory",
|
||||
"@otherDirectoryDescription": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"storageAccessDialogTitle": "Storage Access",
|
||||
"@storageAccessDialogTitle": {},
|
||||
"storageVolumeAccessDialogMessage": "Please select the {directory} of “{volume}” in the next screen to give this app access to it.",
|
||||
"@storageVolumeAccessDialogMessage": {
|
||||
"placeholders": {
|
||||
"directory": {
|
||||
"type": "String"
|
||||
},
|
||||
"volume": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"restrictedAccessDialogTitle": "Restricted Access",
|
||||
"@restrictedAccessDialogTitle": {},
|
||||
"restrictedAccessDialogMessage": "This app is not allowed to modify files in the {directory} of “{volume}”.\n\nPlease use a pre-installed file manager or gallery app to move the items to another directory.",
|
||||
"@restrictedAccessDialogMessage": {
|
||||
"placeholders": {
|
||||
"directory": {
|
||||
"type": "String"
|
||||
},
|
||||
"volume": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notEnoughSpaceDialogTitle": "Not Enough Space",
|
||||
"@notEnoughSpaceDialogTitle": {},
|
||||
"notEnoughSpaceDialogMessage": "This operation needs {neededSize} of free space on “{volume}” to complete, but there is only {freeSize} left.",
|
||||
"@notEnoughSpaceDialogMessage": {
|
||||
"placeholders": {
|
||||
"neededSize": {
|
||||
"type": "String"
|
||||
},
|
||||
"freeSize": {
|
||||
"type": "String"
|
||||
},
|
||||
"volume": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"addShortcutDialogLabel": "Shortcut label",
|
||||
"@addShortcutDialogLabel": {},
|
||||
"addShortcutButtonLabel": "ADD",
|
||||
"@addShortcutButtonLabel": {},
|
||||
|
||||
"noMatchingAppDialogTitle": "No Matching App",
|
||||
"@noMatchingAppDialogTitle": {},
|
||||
"noMatchingAppDialogMessage": "There are no apps that can handle this.",
|
||||
"@noMatchingAppDialogMessage": {},
|
||||
|
||||
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Are you sure you want to delete this item?} other{Are you sure you want to delete these {count} items?}}",
|
||||
"@deleteEntriesConfirmationDialogMessage": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
|
||||
"hideFilterConfirmationDialogMessage": "Matching photos and videos will be hidden from your collection. You can show them again from the “Privacy” settings.\n\nAre you sure you want to hide them?",
|
||||
"@hideFilterConfirmationDialogMessage": {},
|
||||
|
||||
"newAlbumDialogTitle": "New Album",
|
||||
"@newAlbumDialogTitle": {},
|
||||
"newAlbumDialogNameLabel": "Album name",
|
||||
"@newAlbumDialogNameLabel": {},
|
||||
"newAlbumDialogNameLabelAlreadyExistsHelper": "Directory already exists",
|
||||
"@newAlbumDialogNameLabelAlreadyExistsHelper": {},
|
||||
"newAlbumDialogStorageLabel": "Storage:",
|
||||
"@newAlbumDialogStorageLabel": {},
|
||||
|
||||
"renameAlbumDialogLabel": "New name",
|
||||
"@renameAlbumDialogLabel": {},
|
||||
"renameAlbumDialogLabelAlreadyExistsHelper": "Directory already exists",
|
||||
"@renameAlbumDialogLabelAlreadyExistsHelper": {},
|
||||
|
||||
"deleteAlbumConfirmationDialogMessage": "{count, plural, =1{Are you sure you want to delete this album and its item?} other{Are you sure you want to delete this album and its {count} items?}}",
|
||||
"@deleteAlbumConfirmationDialogMessage": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
|
||||
"renameEntryDialogLabel": "New name",
|
||||
"@renameEntryDialogLabel": {},
|
||||
|
||||
"genericSuccessFeedback": "Done!",
|
||||
"@genericSuccessFeedback": {},
|
||||
"genericFailureFeedback": "Failed",
|
||||
"@genericFailureFeedback": {},
|
||||
|
||||
"menuActionSort": "Sort",
|
||||
"@menuActionSort": {},
|
||||
"menuActionGroup": "Group",
|
||||
"@menuActionGroup": {},
|
||||
"menuActionStats": "Stats",
|
||||
"@menuActionStats": {},
|
||||
|
||||
"aboutPageTitle": "About",
|
||||
"@aboutPageTitle": {},
|
||||
"aboutFlutter": "Flutter",
|
||||
"@aboutFlutter": {},
|
||||
"aboutUpdate": "New Version Available",
|
||||
"@aboutUpdate": {},
|
||||
"aboutUpdateLinks1": "A new version of Aves is available on",
|
||||
"@aboutUpdateLinks1": {},
|
||||
"aboutUpdateLinks2": "and",
|
||||
"@aboutUpdateLinks2": {},
|
||||
"aboutUpdateLinks3": ".",
|
||||
"@aboutUpdateLinks3": {},
|
||||
"aboutUpdateGithub": "Github",
|
||||
"@aboutUpdateGithub": {},
|
||||
"aboutUpdateGooglePlay": "Google Play",
|
||||
"@aboutUpdateGooglePlay": {},
|
||||
"aboutCredits": "Credits",
|
||||
"@aboutCredits": {},
|
||||
"aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from",
|
||||
"@aboutCreditsWorldAtlas1": {},
|
||||
"aboutCreditsWorldAtlas2": "under ISC License.",
|
||||
"@aboutCreditsWorldAtlas2": {},
|
||||
"aboutLicenses": "Open-Source Licenses",
|
||||
"@aboutLicenses": {},
|
||||
"aboutLicensesBanner": "The following sets forth attribution notices for third-party software that may be contained in this application.",
|
||||
"@aboutLicensesBanner": {},
|
||||
"aboutLicensesSortTooltip": "Sort",
|
||||
"@aboutLicensesSortTooltip": {},
|
||||
"aboutLicensesSortByName": "Sort by name",
|
||||
"@aboutLicensesSortByName": {},
|
||||
"aboutLicensesSortByLicense": "Sort by license",
|
||||
"@aboutLicensesSortByLicense": {},
|
||||
"aboutLicensesAndroidLibraries": "Android Libraries",
|
||||
"@aboutLicensesAndroidLibraries": {},
|
||||
"aboutLicensesFlutterPlugins": "Flutter Plugins",
|
||||
"@aboutLicensesFlutterPlugins": {},
|
||||
"aboutLicensesFlutterPackages": "Flutter Packages",
|
||||
"@aboutLicensesFlutterPackages": {},
|
||||
"aboutLicensesDartPackages": "Dart Packages",
|
||||
"@aboutLicensesDartPackages": {},
|
||||
"aboutLicensesShowAllButtonLabel": "SHOW ALL LICENSES",
|
||||
"@aboutLicensesShowAllButtonLabel": {},
|
||||
|
||||
"collectionPageTitle": "Collection",
|
||||
"@collectionPageTitle": {},
|
||||
"collectionPickPageTitle": "Pick",
|
||||
"@collectionPickPageTitle": {},
|
||||
"collectionSelectionPageTitle": "{count, plural, =0{Select items} =1{1 item} other{{count} items}}",
|
||||
"@collectionSelectionPageTitle": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
|
||||
"collectionActionAddShortcut": "Add shortcut",
|
||||
"@collectionActionAddShortcut": {},
|
||||
"collectionActionSelect": "Select",
|
||||
"@collectionActionSelect": {},
|
||||
"collectionActionSelectAll": "Select all",
|
||||
"@collectionActionSelectAll": {},
|
||||
"collectionActionSelectNone": "Select none",
|
||||
"@collectionActionSelectNone": {},
|
||||
"collectionActionCopy": "Copy to album",
|
||||
"@collectionActionCopy": {},
|
||||
"collectionActionMove": "Move to album",
|
||||
"@collectionActionMove": {},
|
||||
"collectionActionRefreshMetadata": "Refresh metadata",
|
||||
"@collectionActionRefreshMetadata": {},
|
||||
|
||||
"collectionSortTitle": "Sort",
|
||||
"@collectionSortTitle": {},
|
||||
"collectionSortDate": "By date",
|
||||
"@collectionSortDate": {},
|
||||
"collectionSortSize": "By size",
|
||||
"@collectionSortSize": {},
|
||||
"collectionSortName": "By album & file name",
|
||||
"@collectionSortName": {},
|
||||
|
||||
"collectionGroupTitle": "Group",
|
||||
"@collectionGroupTitle": {},
|
||||
"collectionGroupAlbum": "By album",
|
||||
"@collectionGroupAlbum": {},
|
||||
"collectionGroupMonth": "By month",
|
||||
"@collectionGroupMonth": {},
|
||||
"collectionGroupDay": "By day",
|
||||
"@collectionGroupDay": {},
|
||||
"collectionGroupNone": "Do not group",
|
||||
"@collectionGroupNone": {},
|
||||
|
||||
"sectionUnknown": "Unknown",
|
||||
"@sectionUnknown": {},
|
||||
"dateToday": "Today",
|
||||
"@dateToday": {},
|
||||
"dateYesterday": "Yesterday",
|
||||
"@dateYesterday": {},
|
||||
"dateThisMonth": "This month",
|
||||
"@dateThisMonth": {},
|
||||
"errorUnsupportedMimeType": "{mimeType} not supported",
|
||||
"@errorUnsupportedMimeType": {
|
||||
"placeholders": {
|
||||
"mimeType": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"collectionDeleteFailureFeedback": "{count, plural, =1{Failed to delete 1 item} other{Failed to delete {count} items}}",
|
||||
"@collectionDeleteFailureFeedback": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"collectionCopyFailureFeedback": "{count, plural, =1{Failed to copy 1 item} other{Failed to copy {count} items}}",
|
||||
"@collectionCopyFailureFeedback": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"collectionMoveFailureFeedback": "{count, plural, =1{Failed to move 1 item} other{Failed to move {count} items}}",
|
||||
"@collectionMoveFailureFeedback": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"collectionExportFailureFeedback": "{count, plural, =1{Failed to export 1 page} other{Failed to export {count} pages}}",
|
||||
"@collectionExportFailureFeedback": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"collectionCopySuccessFeedback": "{count, plural, =1{Copied 1 item} other{Copied {count} items}}",
|
||||
"@collectionCopySuccessFeedback": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"collectionMoveSuccessFeedback": "{count, plural, =1{Moved 1 item} other{Moved {count} items}}",
|
||||
"@collectionMoveSuccessFeedback": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
|
||||
"collectionEmptyFavourites": "No favourites",
|
||||
"@collectionEmptyFavourites": {},
|
||||
"collectionEmptyVideos": "No videos",
|
||||
"@collectionEmptyVideos": {},
|
||||
"collectionEmptyImages": "No images",
|
||||
"@collectionEmptyImages": {},
|
||||
|
||||
"collectionSelectSectionTooltip": "Select section",
|
||||
"@collectionSelectSectionTooltip": {},
|
||||
"collectionDeselectSectionTooltip": "Deselect section",
|
||||
"@collectionDeselectSectionTooltip": {},
|
||||
|
||||
"drawerCollectionAll": "All collection",
|
||||
"@drawerCollectionAll": {},
|
||||
"drawerCollectionVideos": "Videos",
|
||||
"@drawerCollectionVideos": {},
|
||||
"drawerCollectionFavourites": "Favourites",
|
||||
"@drawerCollectionFavourites": {},
|
||||
|
||||
"chipSortTitle": "Sort",
|
||||
"@chipSortTitle": {},
|
||||
"chipSortDate": "By date",
|
||||
"@chipSortDate": {},
|
||||
"chipSortName": "By name",
|
||||
"@chipSortName": {},
|
||||
"chipSortCount": "By item count",
|
||||
"@chipSortCount": {},
|
||||
|
||||
"albumGroupTitle": "Group",
|
||||
"@albumGroupTitle": {},
|
||||
"albumGroupTier": "By tier",
|
||||
"@albumGroupTier": {},
|
||||
"albumGroupVolume": "By storage volume",
|
||||
"@albumGroupVolume": {},
|
||||
"albumGroupNone": "Do not group",
|
||||
"@albumGroupNone": {},
|
||||
|
||||
"albumPickPageTitleCopy": "Copy to Album",
|
||||
"@albumPickPageTitleCopy": {},
|
||||
"albumPickPageTitleExport": "Export to Album",
|
||||
"@albumPickPageTitleExport": {},
|
||||
"albumPickPageTitleMove": "Move to Album",
|
||||
"@albumPickPageTitleMove": {},
|
||||
|
||||
"albumPageTitle": "Albums",
|
||||
"@albumPageTitle": {},
|
||||
"albumEmpty": "No albums",
|
||||
"@albumEmpty": {},
|
||||
"createAlbumTooltip": "Create album",
|
||||
"@createAlbumTooltip": {},
|
||||
"createAlbumButtonLabel": "CREATE",
|
||||
"@createAlbumButtonLabel": {},
|
||||
|
||||
"countryPageTitle": "Countries",
|
||||
"@countryPageTitle": {},
|
||||
"countryEmpty": "No countries",
|
||||
"@countryEmpty": {},
|
||||
|
||||
"tagPageTitle": "Tags",
|
||||
"@tagPageTitle": {},
|
||||
"tagEmpty": "No tags",
|
||||
"@tagEmpty": {},
|
||||
|
||||
"searchCollectionFieldHint": "Search collection",
|
||||
"@searchCollectionFieldHint": {},
|
||||
"searchSectionRecent": "Recent",
|
||||
"@searchSectionRecent": {},
|
||||
"searchSectionAlbums": "Albums",
|
||||
"@searchSectionAlbums": {},
|
||||
"searchSectionCountries": "Countries",
|
||||
"@searchSectionCountries": {},
|
||||
"searchSectionPlaces": "Places",
|
||||
"@searchSectionPlaces": {},
|
||||
"searchSectionTags": "Tags",
|
||||
"@searchSectionTags": {},
|
||||
|
||||
"settingsPageTitle": "Settings",
|
||||
"@settingsPageTitle": {},
|
||||
"settingsSystemDefault": "System",
|
||||
"@settingsSystemDefault": {},
|
||||
|
||||
"settingsSectionNavigation": "Navigation",
|
||||
"@settingsSectionNavigation": {},
|
||||
"settingsHome": "Home",
|
||||
"@settingsHome": {},
|
||||
"settingsDoubleBackExit": "Tap “back” twice to exit",
|
||||
"@settingsDoubleBackExit": {},
|
||||
|
||||
"settingsSectionDisplay": "Display",
|
||||
"@settingsSectionDisplay": {},
|
||||
"settingsLanguage": "Language",
|
||||
"@settingsLanguage": {},
|
||||
"settingsKeepScreenOnTile": "Keep screen on",
|
||||
"@settingsKeepScreenOnTile": {},
|
||||
"settingsKeepScreenOnTitle": "Keep Screen On",
|
||||
"@settingsKeepScreenOnTitle": {},
|
||||
"settingsRasterImageBackground": "Raster image background",
|
||||
"@settingsRasterImageBackground": {},
|
||||
"settingsVectorImageBackground": "Vector image background",
|
||||
"@settingsVectorImageBackground": {},
|
||||
"settingsCoordinateFormatTile": "Coordinate format",
|
||||
"@settingsCoordinateFormatTile": {},
|
||||
"settingsCoordinateFormatTitle": "Coordinate Format",
|
||||
"@settingsCoordinateFormatTitle": {},
|
||||
|
||||
"settingsSectionThumbnails": "Thumbnails",
|
||||
"@settingsSectionThumbnails": {},
|
||||
"settingsThumbnailShowLocationIcon": "Show location icon",
|
||||
"@settingsThumbnailShowLocationIcon": {},
|
||||
"settingsThumbnailShowRawIcon": "Show raw icon",
|
||||
"@settingsThumbnailShowRawIcon": {},
|
||||
"settingsThumbnailShowVideoDuration": "Show video duration",
|
||||
"@settingsThumbnailShowVideoDuration": {},
|
||||
|
||||
"settingsSectionViewer": "Viewer",
|
||||
"@settingsSectionViewer": {},
|
||||
"settingsViewerShowMinimap": "Show minimap",
|
||||
"@settingsViewerShowMinimap": {},
|
||||
"settingsViewerShowInformation": "Show information",
|
||||
"@settingsViewerShowInformation": {},
|
||||
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",
|
||||
"@settingsViewerShowInformationSubtitle": {},
|
||||
"settingsViewerShowShootingDetails": "Show shooting details",
|
||||
"@settingsViewerShowShootingDetails": {},
|
||||
|
||||
"settingsSectionSearch": "Search",
|
||||
"@settingsSectionSearch": {},
|
||||
"settingsSaveSearchHistory": "Save search history",
|
||||
"@settingsSaveSearchHistory": {},
|
||||
|
||||
"settingsSectionPrivacy": "Privacy",
|
||||
"@settingsSectionPrivacy": {},
|
||||
"settingsEnableAnalytics": "Allow anonymous analytics and crash reporting",
|
||||
"@settingsEnableAnalytics": {},
|
||||
|
||||
"settingsHiddenFiltersTile": "Hidden filters",
|
||||
"@settingsHiddenFiltersTile": {},
|
||||
"settingsHiddenFiltersTitle": "Hidden Filters",
|
||||
"@settingsHiddenFiltersTitle": {},
|
||||
"settingsHiddenFiltersBanner": "Photos and videos matching hidden filters will not appear in your collection.",
|
||||
"@settingsHiddenFiltersBanner": {},
|
||||
"settingsHiddenFiltersEmpty": "No hidden filters",
|
||||
"@settingsHiddenFiltersEmpty": {},
|
||||
|
||||
"settingsStorageAccessTile": "Storage access",
|
||||
"@settingsStorageAccessTile": {},
|
||||
"settingsStorageAccessTitle": "Storage Access",
|
||||
"@settingsStorageAccessTitle": {},
|
||||
"settingsStorageAccessBanner": "Some directories require an explicit access grant to modify files in them. You can review here directories to which you previously gave access.",
|
||||
"@settingsStorageAccessBanner": {},
|
||||
"settingsStorageAccessEmpty": "No access grants",
|
||||
"@settingsStorageAccessEmpty": {},
|
||||
"settingsStorageAccessRevokeTooltip": "Revoke",
|
||||
"@settingsStorageAccessRevokeTooltip": {},
|
||||
|
||||
"statsPageTitle": "Stats",
|
||||
"@statsPageTitle": {},
|
||||
"statsImage": "{count, plural, =1{image} other{images}}",
|
||||
"@statsImage": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"statsVideo": "{count, plural, =1{video} other{videos}}",
|
||||
"@statsVideo": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"statsWithGps": "{count, plural, =1{1 item with location} other{{count} items with location}}",
|
||||
"@statsWithGps": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"statsTopCountries": "Top Countries",
|
||||
"@statsTopCountries": {},
|
||||
"statsTopPlaces": "Top Places",
|
||||
"@statsTopPlaces": {},
|
||||
"statsTopTags": "Top Tags",
|
||||
"@statsTopTags": {},
|
||||
|
||||
"viewerOpenPanoramaButtonLabel": "OPEN PANORAMA",
|
||||
"@viewerOpenPanoramaButtonLabel": {},
|
||||
"viewerOpenTooltip": "Open",
|
||||
"@viewerOpenTooltip": {},
|
||||
"viewerPauseTooltip": "Pause",
|
||||
"@viewerPauseTooltip": {},
|
||||
"viewerPlayTooltip": "Play",
|
||||
"@viewerPlayTooltip": {},
|
||||
"viewerErrorUnknown": "Oops!",
|
||||
"@viewerErrorUnknown": {},
|
||||
"viewerErrorDoesNotExist": "The file no longer exists.",
|
||||
"@viewerErrorDoesNotExist": {},
|
||||
|
||||
"viewerInfoPageTitle": "Info",
|
||||
"@viewerInfoPageTitle": {},
|
||||
"viewerInfoBackToViewerTooltip": "Back to viewer",
|
||||
"@viewerInfoBackToViewerTooltip": {},
|
||||
|
||||
"viewerInfoUnknown": "unknown",
|
||||
"@viewerInfoUnknown": {},
|
||||
"viewerInfoLabelTitle": "Title",
|
||||
"@viewerInfoLabelTitle": {},
|
||||
"viewerInfoLabelDate": "Date",
|
||||
"@viewerInfoLabelDate": {},
|
||||
"viewerInfoLabelResolution": "Resolution",
|
||||
"@viewerInfoLabelResolution": {},
|
||||
"viewerInfoLabelSize": "Size",
|
||||
"@viewerInfoLabelSize": {},
|
||||
"viewerInfoLabelUri": "URI",
|
||||
"@viewerInfoLabelUri": {},
|
||||
"viewerInfoLabelPath": "Path",
|
||||
"@viewerInfoLabelPath": {},
|
||||
"viewerInfoLabelDuration": "Duration",
|
||||
"@viewerInfoLabelDuration": {},
|
||||
"viewerInfoLabelOwner": "Owned by",
|
||||
"@viewerInfoLabelOwner": {},
|
||||
"viewerInfoLabelCoordinates": "Coordinates",
|
||||
"@viewerInfoLabelCoordinates": {},
|
||||
"viewerInfoLabelAddress": "Address",
|
||||
"@viewerInfoLabelAddress": {},
|
||||
|
||||
"viewerInfoMapStyleTitle": "Map Style",
|
||||
"@viewerInfoMapStyleTitle": {},
|
||||
"viewerInfoMapStyleTooltip": "Select map style",
|
||||
"@viewerInfoMapStyleTooltip": {},
|
||||
"viewerInfoMapZoomInTooltip": "Zoom in",
|
||||
"@viewerInfoMapZoomInTooltip": {},
|
||||
"viewerInfoMapZoomOutTooltip": "Zoom out",
|
||||
"@viewerInfoMapZoomOutTooltip": {},
|
||||
"mapAttributionOsmHot": "Map data © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors • Tiles by [HOT](https://www.hotosm.org/) • Hosted by [OSM France](https://openstreetmap.fr/)",
|
||||
"@mapAttributionOsmHot": {},
|
||||
"mapAttributionStamen": "Map data © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors • Tiles by [Stamen Design](http://stamen.com), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0)",
|
||||
"@mapAttributionStamen": {},
|
||||
|
||||
"viewerInfoOpenEmbeddedFailureFeedback": "Failed to extract embedded data",
|
||||
"@viewerInfoOpenEmbeddedFailureFeedback": {},
|
||||
"viewerInfoOpenLinkText": "Open",
|
||||
"@viewerInfoOpenLinkText": {},
|
||||
"viewerInfoViewXmlLinkText": "View XML",
|
||||
"@viewerInfoViewXmlLinkText": {},
|
||||
|
||||
"viewerInfoSearchFieldLabel": "Search metadata",
|
||||
"@viewerInfoSearchFieldLabel": {},
|
||||
"viewerInfoSearchEmpty": "No matching keys",
|
||||
"@viewerInfoSearchEmpty": {},
|
||||
"viewerInfoSearchSuggestionDate": "Date & time",
|
||||
"@viewerInfoSearchSuggestionDate": {},
|
||||
"viewerInfoSearchSuggestionDescription": "Description",
|
||||
"@viewerInfoSearchSuggestionDescription": {},
|
||||
"viewerInfoSearchSuggestionDimensions": "Dimensions",
|
||||
"@viewerInfoSearchSuggestionDimensions": {},
|
||||
"viewerInfoSearchSuggestionResolution": "Resolution",
|
||||
"@viewerInfoSearchSuggestionResolution": {},
|
||||
"viewerInfoSearchSuggestionRights": "Rights",
|
||||
"@viewerInfoSearchSuggestionRights": {},
|
||||
|
||||
"panoramaEnableSensorControl": "Enable sensor control",
|
||||
"@panoramaEnableSensorControl": {},
|
||||
"panoramaDisableSensorControl": "Disable sensor control",
|
||||
"@panoramaDisableSensorControl": {},
|
||||
|
||||
"sourceViewerPageTitle": "Source",
|
||||
"@sourceViewerPageTitle": {}
|
||||
}
|
8
lib/l10n/app_ko.arb
Normal file
8
lib/l10n/app_ko.arb
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"appName": "아베스",
|
||||
|
||||
"collectionSelectionPageTitle": "{count, plural, =0{항목 선택} other{{count}개}}",
|
||||
|
||||
"settingsLanguage": "언어",
|
||||
"settingsSystemDefault": "시스템"
|
||||
}
|
|
@ -9,6 +9,7 @@ import 'package:aves/theme/icons.dart';
|
|||
import 'package:aves/utils/debouncer.dart';
|
||||
import 'package:aves/widgets/common/behaviour/route_tracker.dart';
|
||||
import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
|
||||
import 'package:aves/widgets/home_page.dart';
|
||||
import 'package:aves/widgets/welcome_page.dart';
|
||||
|
@ -19,6 +20,8 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
||||
import 'package:overlay_support/overlay_support.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -120,19 +123,30 @@ class _AvesAppState extends State<AvesApp> {
|
|||
child: FutureBuilder<void>(
|
||||
future: _appSetup,
|
||||
builder: (context, snapshot) {
|
||||
final home = (!snapshot.hasError && snapshot.connectionState == ConnectionState.done)
|
||||
final initialized = !snapshot.hasError && snapshot.connectionState == ConnectionState.done;
|
||||
final home = initialized
|
||||
? getFirstPage()
|
||||
: Scaffold(
|
||||
body: snapshot.hasError ? _buildError(snapshot.error) : SizedBox.shrink(),
|
||||
body: snapshot.hasError ? _buildError(snapshot.error) : SizedBox(),
|
||||
);
|
||||
return MaterialApp(
|
||||
navigatorKey: _navigatorKey,
|
||||
home: home,
|
||||
navigatorObservers: _navigatorObservers,
|
||||
title: 'Aves',
|
||||
darkTheme: darkTheme,
|
||||
themeMode: ThemeMode.dark,
|
||||
);
|
||||
return Selector<Settings, Locale>(
|
||||
selector: (context, s) => s.locale,
|
||||
builder: (context, settingsLocale, child) {
|
||||
return MaterialApp(
|
||||
navigatorKey: _navigatorKey,
|
||||
home: home,
|
||||
navigatorObservers: _navigatorObservers,
|
||||
onGenerateTitle: (context) => context.l10n.appName,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: ThemeMode.dark,
|
||||
locale: settingsLocale,
|
||||
localizationsDelegates: [
|
||||
...AppLocalizations.localizationsDelegates,
|
||||
LocaleNamesLocalizationsDelegate(),
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum ChipSetAction {
|
||||
group,
|
||||
sort,
|
||||
refresh,
|
||||
stats,
|
||||
}
|
||||
|
||||
|
@ -20,24 +20,24 @@ enum ChipAction {
|
|||
}
|
||||
|
||||
extension ExtraChipAction on ChipAction {
|
||||
String getText() {
|
||||
String getText(BuildContext context) {
|
||||
switch (this) {
|
||||
case ChipAction.delete:
|
||||
return 'Delete';
|
||||
return context.l10n.chipActionDelete;
|
||||
case ChipAction.goToAlbumPage:
|
||||
return 'Show in Albums';
|
||||
return context.l10n.chipActionGoToAlbumPage;
|
||||
case ChipAction.goToCountryPage:
|
||||
return 'Show in Countries';
|
||||
return context.l10n.chipActionGoToCountryPage;
|
||||
case ChipAction.goToTagPage:
|
||||
return 'Show in Tags';
|
||||
return context.l10n.chipActionGoToTagPage;
|
||||
case ChipAction.hide:
|
||||
return 'Hide';
|
||||
return context.l10n.chipActionHide;
|
||||
case ChipAction.pin:
|
||||
return 'Pin to top';
|
||||
return context.l10n.chipActionPin;
|
||||
case ChipAction.unpin:
|
||||
return 'Unpin from top';
|
||||
return context.l10n.chipActionUnpin;
|
||||
case ChipAction.rename:
|
||||
return 'Rename';
|
||||
return context.l10n.chipActionRename;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ enum CollectionAction {
|
|||
addShortcut,
|
||||
sort,
|
||||
group,
|
||||
refresh,
|
||||
select,
|
||||
selectAll,
|
||||
selectNone,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum EntryAction {
|
||||
|
@ -46,41 +47,41 @@ class EntryActions {
|
|||
}
|
||||
|
||||
extension ExtraEntryAction on EntryAction {
|
||||
String getText() {
|
||||
String getText(BuildContext context) {
|
||||
switch (this) {
|
||||
// in app actions
|
||||
case EntryAction.toggleFavourite:
|
||||
// different data depending on toggle state
|
||||
return null;
|
||||
case EntryAction.delete:
|
||||
return 'Delete';
|
||||
return context.l10n.entryActionDelete;
|
||||
case EntryAction.export:
|
||||
return 'Export';
|
||||
return context.l10n.entryActionExport;
|
||||
case EntryAction.info:
|
||||
return 'Info';
|
||||
return context.l10n.entryActionInfo;
|
||||
case EntryAction.rename:
|
||||
return 'Rename';
|
||||
return context.l10n.entryActionRename;
|
||||
case EntryAction.rotateCCW:
|
||||
return 'Rotate counterclockwise';
|
||||
return context.l10n.entryActionRotateCCW;
|
||||
case EntryAction.rotateCW:
|
||||
return 'Rotate clockwise';
|
||||
return context.l10n.entryActionRotateCW;
|
||||
case EntryAction.flip:
|
||||
return 'Flip horizontally';
|
||||
return context.l10n.entryActionFlip;
|
||||
case EntryAction.print:
|
||||
return 'Print';
|
||||
return context.l10n.entryActionPrint;
|
||||
case EntryAction.share:
|
||||
return 'Share';
|
||||
return context.l10n.entryActionShare;
|
||||
case EntryAction.viewSource:
|
||||
return 'View source';
|
||||
return context.l10n.entryActionViewSource;
|
||||
// external app actions
|
||||
case EntryAction.edit:
|
||||
return 'Edit with…';
|
||||
return context.l10n.entryActionEdit;
|
||||
case EntryAction.open:
|
||||
return 'Open with…';
|
||||
return context.l10n.entryActionOpen;
|
||||
case EntryAction.setAs:
|
||||
return 'Set as…';
|
||||
return context.l10n.entryActionSetAs;
|
||||
case EntryAction.openMap:
|
||||
return 'Show on map…';
|
||||
return context.l10n.entryActionOpenMap;
|
||||
case EntryAction.debug:
|
||||
return 'Debug';
|
||||
}
|
||||
|
|
|
@ -35,10 +35,10 @@ class AlbumFilter extends CollectionFilter {
|
|||
EntryFilter get test => (entry) => entry.directory == album;
|
||||
|
||||
@override
|
||||
String get label => uniqueName ?? album.split(separator).last;
|
||||
String get universalLabel => uniqueName ?? album.split(separator).last;
|
||||
|
||||
@override
|
||||
String get tooltip => album;
|
||||
String getTooltip(BuildContext context) => album;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) {
|
||||
|
@ -74,7 +74,10 @@ class AlbumFilter extends CollectionFilter {
|
|||
}
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => '$type-$album';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class FavouriteFilter extends CollectionFilter {
|
||||
static const type = 'favourite';
|
||||
|
||||
const FavouriteFilter();
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => {
|
||||
'type': type,
|
||||
|
@ -15,13 +19,22 @@ class FavouriteFilter extends CollectionFilter {
|
|||
EntryFilter get test => (entry) => entry.isFavourite;
|
||||
|
||||
@override
|
||||
String get label => 'Favourite';
|
||||
String get universalLabel => type;
|
||||
|
||||
@override
|
||||
String getLabel(BuildContext context) => context.l10n.filterFavouriteLabel;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) => Icon(AIcons.favourite, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
Future<Color> color(BuildContext context) => SynchronousFuture(Colors.red);
|
||||
|
||||
@override
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => type;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
abstract class CollectionFilter implements Comparable<CollectionFilter> {
|
||||
static const List<String> collectionFilterOrder = [
|
||||
static const List<String> categoryOrder = [
|
||||
QueryFilter.type,
|
||||
FavouriteFilter.type,
|
||||
MimeFilter.type,
|
||||
|
@ -57,25 +57,28 @@ abstract class CollectionFilter implements Comparable<CollectionFilter> {
|
|||
|
||||
bool get isUnique => true;
|
||||
|
||||
String get label;
|
||||
String get universalLabel;
|
||||
|
||||
String get tooltip => label;
|
||||
String getLabel(BuildContext context) => universalLabel;
|
||||
|
||||
String getTooltip(BuildContext context) => getLabel(context);
|
||||
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false});
|
||||
|
||||
Future<Color> color(BuildContext context) => SynchronousFuture(stringToColor(label));
|
||||
Future<Color> color(BuildContext context) => SynchronousFuture(stringToColor(getLabel(context)));
|
||||
|
||||
String get typeKey;
|
||||
|
||||
int get displayPriority => collectionFilterOrder.indexOf(typeKey);
|
||||
String get category;
|
||||
|
||||
// to be used as widget key
|
||||
String get key => '$typeKey-$label';
|
||||
String get key;
|
||||
|
||||
int get displayPriority => categoryOrder.indexOf(category);
|
||||
|
||||
@override
|
||||
int compareTo(CollectionFilter other) {
|
||||
final c = displayPriority.compareTo(other.displayPriority);
|
||||
return c != 0 ? c : compareAsciiUpperCase(label, other.label);
|
||||
// assume we compare context-independent labels
|
||||
return c != 0 ? c : compareAsciiUpperCase(universalLabel, other.universalLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class LocationFilter extends CollectionFilter {
|
||||
static const type = 'location';
|
||||
static const emptyLabel = 'unlocated';
|
||||
static const locationSeparator = ';';
|
||||
|
||||
final LocationLevel level;
|
||||
|
@ -48,7 +48,10 @@ class LocationFilter extends CollectionFilter {
|
|||
EntryFilter get test => _test;
|
||||
|
||||
@override
|
||||
String get label => _location.isEmpty ? emptyLabel : _location;
|
||||
String get universalLabel => _location;
|
||||
|
||||
@override
|
||||
String getLabel(BuildContext context) => _location.isEmpty ? context.l10n.filterLocationEmptyLabel : _location;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) {
|
||||
|
@ -66,7 +69,10 @@ class LocationFilter extends CollectionFilter {
|
|||
}
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => '$type-$level-$_location';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/mime_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
|
@ -17,14 +19,12 @@ class MimeFilter extends CollectionFilter {
|
|||
if (lowMime.endsWith('/*')) {
|
||||
lowMime = lowMime.substring(0, lowMime.length - 2);
|
||||
_test = (entry) => entry.mimeType.startsWith(lowMime);
|
||||
if (lowMime == 'video') {
|
||||
_label = 'Video';
|
||||
_icon = AIcons.video;
|
||||
} else if (lowMime == 'image') {
|
||||
_label = 'Image';
|
||||
_label = lowMime.toUpperCase();
|
||||
if (mime == MimeTypes.anyImage) {
|
||||
_icon = AIcons.image;
|
||||
} else if (mime == MimeTypes.anyVideo) {
|
||||
_icon = AIcons.video;
|
||||
}
|
||||
_label ??= lowMime.split('/')[0].toUpperCase();
|
||||
} else {
|
||||
_test = (entry) => entry.mimeType == lowMime;
|
||||
_label = MimeUtils.displayType(lowMime);
|
||||
|
@ -47,13 +47,28 @@ class MimeFilter extends CollectionFilter {
|
|||
EntryFilter get test => _test;
|
||||
|
||||
@override
|
||||
String get label => _label;
|
||||
String get universalLabel => _label;
|
||||
|
||||
@override
|
||||
String getLabel(BuildContext context) {
|
||||
switch (mime) {
|
||||
case MimeTypes.anyImage:
|
||||
return context.l10n.filterMimeImageLabel;
|
||||
case MimeTypes.anyVideo:
|
||||
return context.l10n.filterMimeVideoLabel;
|
||||
default:
|
||||
return _label;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) => Icon(_icon, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => '$type-$mime';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
|
@ -50,7 +50,7 @@ class QueryFilter extends CollectionFilter {
|
|||
bool get isUnique => false;
|
||||
|
||||
@override
|
||||
String get label => '$query';
|
||||
String get universalLabel => query;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) => Icon(AIcons.text, size: size);
|
||||
|
@ -59,7 +59,10 @@ class QueryFilter extends CollectionFilter {
|
|||
Future<Color> color(BuildContext context) => colorful ? super.color(context) : SynchronousFuture(AvesFilterChip.defaultOutlineColor);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => '$type-$query';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class TagFilter extends CollectionFilter {
|
||||
static const type = 'tag';
|
||||
static const emptyLabel = 'untagged';
|
||||
|
||||
final String tag;
|
||||
EntryFilter _test;
|
||||
|
@ -36,13 +36,19 @@ class TagFilter extends CollectionFilter {
|
|||
bool get isUnique => false;
|
||||
|
||||
@override
|
||||
String get label => tag.isEmpty ? emptyLabel : tag;
|
||||
String get universalLabel => tag;
|
||||
|
||||
@override
|
||||
String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterTagEmptyLabel : tag;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) => showGenericIcon ? Icon(tag.isEmpty ? AIcons.tagOff : AIcons.tag, size: size) : null;
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => '$type-$tag';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
|
@ -13,26 +14,26 @@ class TypeFilter extends CollectionFilter {
|
|||
|
||||
final String itemType;
|
||||
EntryFilter _test;
|
||||
String _label;
|
||||
IconData _icon;
|
||||
|
||||
TypeFilter(this.itemType) {
|
||||
if (itemType == animated) {
|
||||
_test = (entry) => entry.isAnimated;
|
||||
_label = 'Animated';
|
||||
_icon = AIcons.animated;
|
||||
} else if (itemType == panorama) {
|
||||
_test = (entry) => entry.isImage && entry.is360;
|
||||
_label = 'Panorama';
|
||||
_icon = AIcons.threesixty;
|
||||
} else if (itemType == sphericalVideo) {
|
||||
_test = (entry) => entry.isVideo && entry.is360;
|
||||
_label = '360° Video';
|
||||
_icon = AIcons.threesixty;
|
||||
} else if (itemType == geotiff) {
|
||||
_test = (entry) => entry.isGeotiff;
|
||||
_label = 'GeoTIFF';
|
||||
_icon = AIcons.geo;
|
||||
switch (itemType) {
|
||||
case animated:
|
||||
_test = (entry) => entry.isAnimated;
|
||||
_icon = AIcons.animated;
|
||||
break;
|
||||
case panorama:
|
||||
_test = (entry) => entry.isImage && entry.is360;
|
||||
_icon = AIcons.threesixty;
|
||||
break;
|
||||
case sphericalVideo:
|
||||
_test = (entry) => entry.isVideo && entry.is360;
|
||||
_icon = AIcons.threesixty;
|
||||
break;
|
||||
case geotiff:
|
||||
_test = (entry) => entry.isGeotiff;
|
||||
_icon = AIcons.geo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,13 +52,32 @@ class TypeFilter extends CollectionFilter {
|
|||
EntryFilter get test => _test;
|
||||
|
||||
@override
|
||||
String get label => _label;
|
||||
String get universalLabel => itemType;
|
||||
|
||||
@override
|
||||
String getLabel(BuildContext context) {
|
||||
switch (itemType) {
|
||||
case animated:
|
||||
return context.l10n.filterTypeAnimatedLabel;
|
||||
case panorama:
|
||||
return context.l10n.filterTypePanoramaLabel;
|
||||
case sphericalVideo:
|
||||
return context.l10n.filterTypeSphericalVideoLabel;
|
||||
case geotiff:
|
||||
return context.l10n.filterTypeGeotiffLabel;
|
||||
default:
|
||||
return itemType;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) => Icon(_icon, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
String get category => type;
|
||||
|
||||
@override
|
||||
String get key => '$type-$itemType';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import 'package:aves/geo/format.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:latlong/latlong.dart';
|
||||
|
||||
enum CoordinateFormat { dms, decimal }
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraCoordinateFormat on CoordinateFormat {
|
||||
String get name {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case CoordinateFormat.dms:
|
||||
return 'DMS';
|
||||
return context.l10n.coordinateFormatDms;
|
||||
case CoordinateFormat.decimal:
|
||||
return 'Decimal degrees';
|
||||
return context.l10n.coordinateFormatDecimal;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
enum EntryBackground { black, white, transparent, checkered }
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraEntryBackground on EntryBackground {
|
||||
bool get isColor {
|
||||
|
|
10
lib/model/settings/enums.dart
Normal file
10
lib/model/settings/enums.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
enum CoordinateFormat { dms, decimal }
|
||||
|
||||
enum EntryBackground { black, white, transparent, checkered }
|
||||
|
||||
enum HomePageSetting { collection, albums }
|
||||
|
||||
// browse providers at https://leaflet-extras.github.io/leaflet-providers/preview/
|
||||
enum EntryMapStyle { googleNormal, googleHybrid, googleTerrain, osmHot, stamenToner, stamenWatercolor }
|
||||
|
||||
enum KeepScreenOn { never, viewerOnly, always }
|
|
@ -1,15 +1,17 @@
|
|||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum HomePageSetting { collection, albums }
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraHomePageSetting on HomePageSetting {
|
||||
String get name {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case HomePageSetting.collection:
|
||||
return 'Collection';
|
||||
return context.l10n.collectionPageTitle;
|
||||
case HomePageSetting.albums:
|
||||
return 'Albums';
|
||||
return context.l10n.albumPageTitle;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
// browse providers at https://leaflet-extras.github.io/leaflet-providers/preview/
|
||||
enum EntryMapStyle { googleNormal, googleHybrid, googleTerrain, osmHot, stamenToner, stamenWatercolor }
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraEntryMapStyle on EntryMapStyle {
|
||||
String get name {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case EntryMapStyle.googleNormal:
|
||||
return 'Google Maps';
|
||||
return context.l10n.mapStyleGoogleNormal;
|
||||
case EntryMapStyle.googleHybrid:
|
||||
return 'Google Maps (Hybrid)';
|
||||
return context.l10n.mapStyleGoogleHybrid;
|
||||
case EntryMapStyle.googleTerrain:
|
||||
return 'Google Maps (Terrain)';
|
||||
return context.l10n.mapStyleGoogleTerrain;
|
||||
case EntryMapStyle.osmHot:
|
||||
return 'Humanitarian OSM';
|
||||
return context.l10n.mapStyleOsmHot;
|
||||
case EntryMapStyle.stamenToner:
|
||||
return 'Stamen Toner';
|
||||
return context.l10n.mapStyleStamenToner;
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
return 'Stamen Watercolor';
|
||||
return context.l10n.mapStyleStamenWatercolor;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import 'package:aves/services/window_service.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum KeepScreenOn { never, viewerOnly, always }
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraKeepScreenOn on KeepScreenOn {
|
||||
String get name {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case KeepScreenOn.never:
|
||||
return 'Never';
|
||||
return context.l10n.keepScreenOnNever;
|
||||
case KeepScreenOn.viewerOnly:
|
||||
return 'Viewer page only';
|
||||
return context.l10n.keepScreenOnViewerOnly;
|
||||
case KeepScreenOn.always:
|
||||
return 'Always';
|
||||
return context.l10n.keepScreenOnAlways;
|
||||
default:
|
||||
return toString();
|
||||
}
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/settings/coordinate_format.dart';
|
||||
import 'package:aves/model/settings/entry_background.dart';
|
||||
import 'package:aves/model/settings/home_page.dart';
|
||||
import 'package:aves/model/settings/map_style.dart';
|
||||
import 'package:aves/model/settings/screen_on.dart';
|
||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../source/enums.dart';
|
||||
import 'enums.dart';
|
||||
|
||||
final Settings settings = Settings._private();
|
||||
|
||||
|
@ -24,6 +20,7 @@ class Settings extends ChangeNotifier {
|
|||
// app
|
||||
static const hasAcceptedTermsKey = 'has_accepted_terms';
|
||||
static const isCrashlyticsEnabledKey = 'is_crashlytics_enabled';
|
||||
static const localeKey = 'locale';
|
||||
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
|
||||
static const keepScreenOnKey = 'keep_screen_on';
|
||||
static const homePageKey = 'home_page';
|
||||
|
@ -99,6 +96,34 @@ class Settings extends ChangeNotifier {
|
|||
unawaited(initFirebase());
|
||||
}
|
||||
|
||||
static const localeSeparator = '-';
|
||||
|
||||
Locale get locale {
|
||||
// exceptionally allow getting locale before settings are initialized
|
||||
final tag = _prefs?.getString(localeKey);
|
||||
if (tag != null) {
|
||||
final codes = tag.split(localeSeparator);
|
||||
return Locale.fromSubtags(
|
||||
languageCode: codes[0],
|
||||
scriptCode: codes[1] == '' ? null : codes[1],
|
||||
countryCode: codes[2] == '' ? null : codes[2],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
set locale(Locale newValue) {
|
||||
String tag;
|
||||
if (newValue != null) {
|
||||
tag = [
|
||||
newValue.languageCode ?? '',
|
||||
newValue.scriptCode ?? '',
|
||||
newValue.countryCode ?? '',
|
||||
].join(localeSeparator);
|
||||
}
|
||||
setAndNotify(localeKey, tag);
|
||||
}
|
||||
|
||||
bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, true);
|
||||
|
||||
set mustBackTwiceToExit(bool newValue) => setAndNotify(mustBackTwiceToExitKey, newValue);
|
||||
|
@ -182,7 +207,7 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set showOverlayInfo(bool newValue) => setAndNotify(showOverlayInfoKey, newValue);
|
||||
|
||||
bool get showOverlayShootingDetails => getBoolOrDefault(showOverlayShootingDetailsKey, true);
|
||||
bool get showOverlayShootingDetails => getBoolOrDefault(showOverlayShootingDetailsKey, false);
|
||||
|
||||
set showOverlayShootingDetails(bool newValue) => setAndNotify(showOverlayShootingDetailsKey, newValue);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/settings/settings.dart';
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
mixin AlbumMixin on SourceBase {
|
||||
|
@ -12,8 +13,8 @@ mixin AlbumMixin on SourceBase {
|
|||
List<String> get rawAlbums => List.unmodifiable(_directories);
|
||||
|
||||
int compareAlbumsByName(String a, String b) {
|
||||
final ua = getUniqueAlbumName(a);
|
||||
final ub = getUniqueAlbumName(b);
|
||||
final ua = getUniqueAlbumName(null, a);
|
||||
final ub = getUniqueAlbumName(null, b);
|
||||
final c = compareAsciiUpperCase(ua, ub);
|
||||
if (c != 0) return c;
|
||||
final va = androidFileUtils.getStorageVolume(a)?.path ?? '';
|
||||
|
@ -23,7 +24,7 @@ mixin AlbumMixin on SourceBase {
|
|||
|
||||
void _notifyAlbumChange() => eventBus.fire(AlbumsChangedEvent());
|
||||
|
||||
String getUniqueAlbumName(String dirPath) {
|
||||
String getUniqueAlbumName(BuildContext context, String dirPath) {
|
||||
String unique(String dirPath, [bool Function(String) test]) {
|
||||
final otherAlbums = _directories.where(test ?? (_) => true).where((item) => item != dirPath);
|
||||
final parts = dirPath.split(separator);
|
||||
|
@ -51,7 +52,7 @@ mixin AlbumMixin on SourceBase {
|
|||
if (volume.isPrimary) {
|
||||
return uniqueNameInVolume;
|
||||
} else {
|
||||
return '$uniqueNameInVolume (${volume.description})';
|
||||
return '$uniqueNameInVolume (${volume.getDescription(context)})';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +100,7 @@ mixin AlbumMixin on SourceBase {
|
|||
invalidateAlbumFilterSummary(directories: emptyAlbums);
|
||||
|
||||
final pinnedFilters = settings.pinnedFilters;
|
||||
emptyAlbums.forEach((album) => pinnedFilters.remove(AlbumFilter(album, getUniqueAlbumName(album))));
|
||||
emptyAlbums.forEach((album) => pinnedFilters.removeWhere((filter) => filter is AlbumFilter && filter.album == album));
|
||||
settings.pinnedFilters = pinnedFilters;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin {
|
|||
void addFilter(CollectionFilter filter) {
|
||||
if (filter == null || filters.contains(filter)) return;
|
||||
if (filter.isUnique) {
|
||||
filters.removeWhere((old) => old.typeKey == filter.typeKey);
|
||||
filters.removeWhere((old) => old.category == filter.category);
|
||||
}
|
||||
filters.add(filter);
|
||||
onFilterChanged();
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:aves/model/metadata.dart';
|
|||
import 'package:aves/model/metadata_db.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/album.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/location.dart';
|
||||
import 'package:aves/model/source/tag.dart';
|
||||
import 'package:aves/services/image_op_events.dart';
|
||||
|
@ -256,8 +257,6 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
|||
}
|
||||
}
|
||||
|
||||
enum SourceState { loading, cataloguing, locating, ready }
|
||||
|
||||
class EntryAddedEvent {
|
||||
final Set<AvesEntry> entries;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
enum Activity { browse, select }
|
||||
|
||||
enum SourceState { loading, cataloguing, locating, ready }
|
||||
|
||||
enum ChipSortFactor { date, name, count }
|
||||
|
||||
enum AlbumChipGroupFactor { none, importance, volume }
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/model/filters/location.dart';
|
|||
import 'package:aves/model/metadata.dart';
|
||||
import 'package:aves/model/metadata_db.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -138,7 +139,7 @@ mixin LocationMixin on SourceBase {
|
|||
}
|
||||
|
||||
// the same country code could be found with different country names
|
||||
// e.g. if the locale changed between geolocating calls
|
||||
// e.g. if the locale changed between geocoding calls
|
||||
// so we merge countries by code, keeping only one name for each code
|
||||
final countriesByCode = Map.fromEntries(locations.map((address) => MapEntry(address.countryCode, address.countryName)).where((kv) => kv.key != null && kv.key.isNotEmpty));
|
||||
final updatedCountries = countriesByCode.entries.map((kv) => '${kv.value}${LocationFilter.locationSeparator}${kv.key}').toList()..sort(compareAsciiUpperCase);
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/favourite_repo.dart';
|
|||
import 'package:aves/model/metadata_db.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/services/image_file_service.dart';
|
||||
import 'package:aves/services/media_store_service.dart';
|
||||
import 'package:aves/services/time_service.dart';
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/model/filters/tag.dart';
|
|||
import 'package:aves/model/metadata.dart';
|
||||
import 'package:aves/model/metadata_db.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@ class AndroidAppService {
|
|||
static Future<bool> edit(String uri, String mimeType) async {
|
||||
try {
|
||||
return await platform.invokeMethod('edit', <String, dynamic>{
|
||||
'title': 'Edit with:',
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
|
@ -54,7 +53,6 @@ class AndroidAppService {
|
|||
static Future<bool> open(String uri, String mimeType) async {
|
||||
try {
|
||||
return await platform.invokeMethod('open', <String, dynamic>{
|
||||
'title': 'Open with:',
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
|
@ -78,7 +76,6 @@ class AndroidAppService {
|
|||
static Future<bool> setAs(String uri, String mimeType) async {
|
||||
try {
|
||||
return await platform.invokeMethod('setAs', <String, dynamic>{
|
||||
'title': 'Set as:',
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
|
@ -94,7 +91,6 @@ class AndroidAppService {
|
|||
final urisByMimeType = groupBy<AvesEntry, String>(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
|
||||
try {
|
||||
return await platform.invokeMethod('share', <String, dynamic>{
|
||||
'title': 'Share via:',
|
||||
'urisByMimeType': urisByMimeType,
|
||||
});
|
||||
} on PlatformException catch (e) {
|
||||
|
@ -106,7 +102,6 @@ class AndroidAppService {
|
|||
static Future<bool> shareSingle(String uri, String mimeType) async {
|
||||
try {
|
||||
return await platform.invokeMethod('share', <String, dynamic>{
|
||||
'title': 'Share via:',
|
||||
'urisByMimeType': {
|
||||
mimeType: [uri]
|
||||
},
|
||||
|
|
|
@ -43,7 +43,6 @@ class AIcons {
|
|||
static const IconData openOutside = Icons.open_in_new_outlined;
|
||||
static const IconData pin = Icons.push_pin_outlined;
|
||||
static const IconData print = Icons.print_outlined;
|
||||
static const IconData refresh = Icons.refresh_outlined;
|
||||
static const IconData rename = Icons.title_outlined;
|
||||
static const IconData rotateLeft = Icons.rotate_left_outlined;
|
||||
static const IconData rotateRight = Icons.rotate_right_outlined;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/services/android_file_service.dart';
|
||||
import 'package:aves/utils/change_notifier.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
@ -115,21 +116,30 @@ class Package {
|
|||
|
||||
@immutable
|
||||
class StorageVolume {
|
||||
final String description, path, state;
|
||||
final String _description, path, state;
|
||||
final bool isPrimary, isRemovable;
|
||||
|
||||
const StorageVolume({
|
||||
this.description,
|
||||
String description,
|
||||
this.isPrimary,
|
||||
this.isRemovable,
|
||||
this.path,
|
||||
this.state,
|
||||
});
|
||||
}) : _description = description;
|
||||
|
||||
String getDescription(BuildContext context) {
|
||||
if (_description != null) return _description;
|
||||
// ideally, the context should always be provided, but in some cases (e.g. album comparison),
|
||||
// this would require numerous additional methods to have the context as argument
|
||||
// for such a minor benefit: fallback volume description on Android < N
|
||||
if (isPrimary) return context?.l10n?.storageVolumeDescriptionFallbackPrimary ?? 'Internal Storage';
|
||||
return context?.l10n?.storageVolumeDescriptionFallbackNonPrimary ?? 'SD card';
|
||||
}
|
||||
|
||||
factory StorageVolume.fromMap(Map map) {
|
||||
final isPrimary = map['isPrimary'] ?? false;
|
||||
return StorageVolume(
|
||||
description: map['description'] ?? (isPrimary ? 'Internal storage' : 'SD card'),
|
||||
description: map['description'],
|
||||
isPrimary: isPrimary,
|
||||
isRemovable: map['isRemovable'] ?? false,
|
||||
path: map['path'] ?? '',
|
||||
|
@ -167,11 +177,9 @@ class VolumeRelativeDirectory {
|
|||
);
|
||||
}
|
||||
|
||||
String get directoryDescription => relativeDir.isEmpty ? 'root' : '“$relativeDir”';
|
||||
|
||||
String get volumeDescription {
|
||||
String getVolumeDescription(BuildContext context) {
|
||||
final volume = androidFileUtils.storageVolumes.firstWhere((volume) => volume.path == volumePath, orElse: () => null);
|
||||
return volume?.description ?? volumePath;
|
||||
return volume?.getDescription(context) ?? volumePath;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -21,7 +21,6 @@ class Constants {
|
|||
);
|
||||
|
||||
static const overlayUnknown = '—'; // em dash
|
||||
static const infoUnknown = 'unknown';
|
||||
|
||||
static final pointNemo = LatLng(-48.876667, -123.393333);
|
||||
|
||||
|
@ -66,61 +65,175 @@ class Constants {
|
|||
),
|
||||
];
|
||||
|
||||
static const List<Dependency> flutterPackages = [
|
||||
Dependency(
|
||||
name: 'Flutter',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/flutter/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/flutter',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Charts',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/google/charts/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/google/charts',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Collection',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/collection/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/collection',
|
||||
),
|
||||
static const List<Dependency> flutterPlugins = [
|
||||
Dependency(
|
||||
name: 'Connectivity',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/connectivity/connectivity/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/connectivity/connectivity',
|
||||
),
|
||||
Dependency(
|
||||
name: 'FlutterFire (Core, Analytics, Crashlytics)',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/FirebaseExtended/flutterfire/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/FirebaseExtended/flutterfire',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Flutter ijkplayer',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/CaiJingLong/flutter_ijkplayer/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/CaiJingLong/flutter_ijkplayer',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Geocoder',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/aloisdeniel/flutter_geocoder/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/aloisdeniel/flutter_geocoder',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Google API Availability',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/Baseflow/flutter-google-api-availability/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/Baseflow/flutter-google-api-availability',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Google Maps for Flutter',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Package Info',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/package_info/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/package_info',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Permission Handler',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/Baseflow/flutter-permission-handler/blob/develop/permission_handler/LICENSE',
|
||||
sourceUrl: 'https://github.com/Baseflow/flutter-permission-handler',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Printing',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/DavBfr/dart_pdf',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Shared Preferences',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/shared_preferences/shared_preferences/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences',
|
||||
),
|
||||
Dependency(
|
||||
name: 'sqflite',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/tekartik/sqflite/blob/master/sqflite/LICENSE',
|
||||
sourceUrl: 'https://github.com/tekartik/sqflite',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Streams Channel',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/loup-v/streams_channel/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/loup-v/streams_channel',
|
||||
),
|
||||
Dependency(
|
||||
name: 'URL Launcher',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher',
|
||||
),
|
||||
];
|
||||
|
||||
static const List<Dependency> dartPackages = [
|
||||
Dependency(
|
||||
name: 'Collection',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/collection/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/collection',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Country Code',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/denixport/dart.country/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/denixport/dart.country',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Decorated Icon',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/benPesso/flutter_decorated_icon/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Event Bus',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/marcojakob/dart-event-bus/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/marcojakob/dart-event-bus',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Github',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/SpinlockLabs/github.dart/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/SpinlockLabs/github.dart',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Intl',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/intl/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/intl',
|
||||
),
|
||||
Dependency(
|
||||
name: 'LatLong',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/MikeMitterer/dart-latlong/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/MikeMitterer/dart-latlong',
|
||||
),
|
||||
Dependency(
|
||||
name: 'PDF for Dart and Flutter',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/DavBfr/dart_pdf',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Pedantic',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/pedantic/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/pedantic',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Tuple',
|
||||
license: 'BSD 2-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/tuple/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/tuple',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Version',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dartninja/version/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dartninja/version',
|
||||
),
|
||||
Dependency(
|
||||
name: 'XML',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/renggli/dart-xml/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/renggli/dart-xml',
|
||||
),
|
||||
];
|
||||
|
||||
static const List<Dependency> flutterPackages = [
|
||||
Dependency(
|
||||
name: 'Charts',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/google/charts/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/google/charts',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Decorated Icon',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/benPesso/flutter_decorated_icon/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Expansion Tile Card',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/Skylled/expansion_tile_card/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/Skylled/expansion_tile_card',
|
||||
),
|
||||
Dependency(
|
||||
name: 'FlutterFire (Core, Analytics, Crashlytics)',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/FirebaseExtended/flutterfire/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/FirebaseExtended/flutterfire',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Flushbar',
|
||||
license: 'Apache 2.0',
|
||||
|
@ -134,10 +247,10 @@ class Constants {
|
|||
sourceUrl: 'https://github.com/git-touch/highlight',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Flutter ijkplayer',
|
||||
name: 'Flutter Localized Locales',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/CaiJingLong/flutter_ijkplayer/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/CaiJingLong/flutter_ijkplayer',
|
||||
licenseUrl: 'https://github.com/guidezpl/flutter-localized-locales/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/guidezpl/flutter-localized-locales',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Flutter Map',
|
||||
|
@ -163,36 +276,6 @@ class Constants {
|
|||
licenseUrl: 'https://github.com/dnfield/flutter_svg/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dnfield/flutter_svg',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Geocoder',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/aloisdeniel/flutter_geocoder/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/aloisdeniel/flutter_geocoder',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Github',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/SpinlockLabs/github.dart/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/SpinlockLabs/github.dart',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Google Maps for Flutter',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Intl',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/intl/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/intl',
|
||||
),
|
||||
Dependency(
|
||||
name: 'LatLong',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/MikeMitterer/dart-latlong/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/MikeMitterer/dart-latlong',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Material Design Icons Flutter',
|
||||
license: 'MIT',
|
||||
|
@ -205,12 +288,6 @@ class Constants {
|
|||
licenseUrl: 'https://github.com/boyan01/overlay_support/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/boyan01/overlay_support',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Package Info',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/package_info/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/package_info',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Palette Generator',
|
||||
license: 'BSD 3-Clause',
|
||||
|
@ -223,84 +300,18 @@ class Constants {
|
|||
licenseUrl: 'https://github.com/zesage/panorama/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/zesage/panorama',
|
||||
),
|
||||
Dependency(
|
||||
name: 'PDF for Dart and Flutter',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/DavBfr/dart_pdf',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Pedantic',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/pedantic/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/pedantic',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Percent Indicator',
|
||||
license: 'BSD 2-Clause',
|
||||
licenseUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Permission Handler',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/Baseflow/flutter-permission-handler/blob/develop/permission_handler/LICENSE',
|
||||
sourceUrl: 'https://github.com/Baseflow/flutter-permission-handler',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Printing',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/DavBfr/dart_pdf',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Provider',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/rrousselGit/provider/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/rrousselGit/provider',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Shared Preferences',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/shared_preferences/shared_preferences/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences',
|
||||
),
|
||||
Dependency(
|
||||
name: 'sqflite',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/tekartik/sqflite/blob/master/sqflite/LICENSE',
|
||||
sourceUrl: 'https://github.com/tekartik/sqflite',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Streams Channel',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/loup-v/streams_channel/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/loup-v/streams_channel',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Tuple',
|
||||
license: 'BSD 2-Clause',
|
||||
licenseUrl: 'https://github.com/dart-lang/tuple/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dart-lang/tuple',
|
||||
),
|
||||
Dependency(
|
||||
name: 'URL Launcher',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE',
|
||||
sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Version',
|
||||
license: 'BSD 3-Clause',
|
||||
licenseUrl: 'https://github.com/dartninja/version/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/dartninja/version',
|
||||
),
|
||||
Dependency(
|
||||
name: 'XML',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://github.com/renggli/dart-xml/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/renggli/dart-xml',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'package:aves/widgets/about/app_ref.dart';
|
||||
import 'package:aves/widgets/about/credits.dart';
|
||||
import 'package:aves/widgets/about/licenses.dart';
|
||||
import 'package:aves/widgets/about/new_version.dart';
|
||||
import 'package:aves/widgets/about/update.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
|
@ -11,7 +12,7 @@ class AboutPage extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('About'),
|
||||
title: Text(context.l10n.aboutPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: CustomScrollView(
|
||||
|
@ -23,7 +24,7 @@ class AboutPage extends StatelessWidget {
|
|||
[
|
||||
AppReference(),
|
||||
Divider(),
|
||||
AboutNewVersion(),
|
||||
AboutUpdate(),
|
||||
AboutCredits(),
|
||||
Divider(),
|
||||
],
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:ui';
|
|||
|
||||
import 'package:aves/flutter_version.dart';
|
||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_logo.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
|
@ -48,7 +49,7 @@ class _AppReferenceState extends State<AppReference> {
|
|||
leading: AvesLogo(
|
||||
size: style.fontSize * MediaQuery.textScaleFactorOf(context) * 1.25,
|
||||
),
|
||||
text: 'Aves ${snapshot.data?.version}',
|
||||
text: '${context.l10n.appName} ${snapshot.data?.version}',
|
||||
url: 'https://github.com/deckerst/aves',
|
||||
textStyle: style,
|
||||
);
|
||||
|
@ -71,7 +72,7 @@ class _AppReferenceState extends State<AppReference> {
|
|||
),
|
||||
),
|
||||
),
|
||||
TextSpan(text: 'Flutter ${version['frameworkVersion']}'),
|
||||
TextSpan(text: '${context.l10n.aboutFlutter} ${version['frameworkVersion']}'),
|
||||
],
|
||||
),
|
||||
style: TextStyle(color: subColor),
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:ui';
|
|||
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AboutCredits extends StatelessWidget {
|
||||
|
@ -16,13 +17,13 @@ class AboutCredits extends StatelessWidget {
|
|||
constraints: BoxConstraints(minHeight: 48),
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text('Credits', style: Constants.titleTextStyle),
|
||||
child: Text(context.l10n.aboutCredits, style: Constants.titleTextStyle),
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: 'This app uses a TopoJSON file from'),
|
||||
TextSpan(text: context.l10n.aboutCreditsWorldAtlas1),
|
||||
WidgetSpan(
|
||||
child: LinkChip(
|
||||
text: 'World Atlas',
|
||||
|
@ -31,7 +32,7 @@ class AboutCredits extends StatelessWidget {
|
|||
),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
),
|
||||
TextSpan(text: 'under ISC License.'),
|
||||
TextSpan(text: context.l10n.aboutCreditsWorldAtlas2),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/theme/icons.dart';
|
|||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||
import 'package:aves/widgets/common/basic/menu_row.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -15,13 +16,15 @@ class Licenses extends StatefulWidget {
|
|||
class _LicensesState extends State<Licenses> {
|
||||
final ValueNotifier<String> _expandedNotifier = ValueNotifier(null);
|
||||
LicenseSort _sort = LicenseSort.name;
|
||||
List<Dependency> _platform, _flutter;
|
||||
List<Dependency> _platform, _flutterPlugins, _flutterPackages, _dartPackages;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_platform = List.from(Constants.androidDependencies);
|
||||
_flutter = List.from(Constants.flutterPackages);
|
||||
_platform = List<Dependency>.from(Constants.androidDependencies);
|
||||
_flutterPlugins = List<Dependency>.from(Constants.flutterPlugins);
|
||||
_flutterPackages = List<Dependency>.from(Constants.flutterPackages);
|
||||
_dartPackages = List<Dependency>.from(Constants.dartPackages);
|
||||
_sortPackages();
|
||||
}
|
||||
|
||||
|
@ -38,7 +41,9 @@ class _LicensesState extends State<Licenses> {
|
|||
}
|
||||
|
||||
_platform.sort(compare);
|
||||
_flutter.sort(compare);
|
||||
_flutterPlugins.sort(compare);
|
||||
_flutterPackages.sort(compare);
|
||||
_dartPackages.sort(compare);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -51,16 +56,28 @@ class _LicensesState extends State<Licenses> {
|
|||
_buildHeader(),
|
||||
SizedBox(height: 16),
|
||||
AvesExpansionTile(
|
||||
title: 'Android Libraries',
|
||||
title: context.l10n.aboutLicensesAndroidLibraries,
|
||||
color: BrandColors.android,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: _platform.map((package) => LicenseRow(package)).toList(),
|
||||
),
|
||||
AvesExpansionTile(
|
||||
title: 'Flutter Packages',
|
||||
title: context.l10n.aboutLicensesFlutterPlugins,
|
||||
color: BrandColors.flutter,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: _flutter.map((package) => LicenseRow(package)).toList(),
|
||||
children: _flutterPlugins.map((package) => LicenseRow(package)).toList(),
|
||||
),
|
||||
AvesExpansionTile(
|
||||
title: context.l10n.aboutLicensesFlutterPackages,
|
||||
color: BrandColors.flutter,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: _flutterPackages.map((package) => LicenseRow(package)).toList(),
|
||||
),
|
||||
AvesExpansionTile(
|
||||
title: context.l10n.aboutLicensesDartPackages,
|
||||
color: BrandColors.flutter,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: _dartPackages.map((package) => LicenseRow(package)).toList(),
|
||||
),
|
||||
Center(
|
||||
child: TextButton(
|
||||
|
@ -76,7 +93,7 @@ class _LicensesState extends State<Licenses> {
|
|||
),
|
||||
),
|
||||
),
|
||||
child: Text('Show All Licenses'.toUpperCase()),
|
||||
child: Text(context.l10n.aboutLicensesShowAllButtonLabel),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -94,17 +111,17 @@ class _LicensesState extends State<Licenses> {
|
|||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text('Open-Source Licenses', style: Constants.titleTextStyle),
|
||||
child: Text(context.l10n.aboutLicenses, style: Constants.titleTextStyle),
|
||||
),
|
||||
PopupMenuButton<LicenseSort>(
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: LicenseSort.name,
|
||||
child: MenuRow(text: 'Sort by name', checked: _sort == LicenseSort.name),
|
||||
child: MenuRow(text: context.l10n.aboutLicensesSortByName, checked: _sort == LicenseSort.name),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: LicenseSort.license,
|
||||
child: MenuRow(text: 'Sort by license', checked: _sort == LicenseSort.license),
|
||||
child: MenuRow(text: context.l10n.aboutLicensesSortByLicense, checked: _sort == LicenseSort.license),
|
||||
),
|
||||
],
|
||||
onSelected: (newSort) {
|
||||
|
@ -112,7 +129,7 @@ class _LicensesState extends State<Licenses> {
|
|||
_sortPackages();
|
||||
setState(() {});
|
||||
},
|
||||
tooltip: 'Sort',
|
||||
tooltip: context.l10n.aboutLicensesSortTooltip,
|
||||
icon: Icon(AIcons.sort),
|
||||
),
|
||||
],
|
||||
|
@ -121,7 +138,7 @@ class _LicensesState extends State<Licenses> {
|
|||
SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('The following sets forth attribution notices for third party software that may be contained in this application.'),
|
||||
child: Text(context.l10n.aboutLicensesBanner),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -2,29 +2,30 @@ import 'package:aves/model/availability.dart';
|
|||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/about/news_badge.dart';
|
||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AboutNewVersion extends StatefulWidget {
|
||||
class AboutUpdate extends StatefulWidget {
|
||||
@override
|
||||
_AboutNewVersionState createState() => _AboutNewVersionState();
|
||||
_AboutUpdateState createState() => _AboutUpdateState();
|
||||
}
|
||||
|
||||
class _AboutNewVersionState extends State<AboutNewVersion> {
|
||||
Future<bool> _newVersionLoader;
|
||||
class _AboutUpdateState extends State<AboutUpdate> {
|
||||
Future<bool> _updateChecker;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_newVersionLoader = availability.isNewVersionAvailable;
|
||||
_updateChecker = availability.isNewVersionAvailable;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<bool>(
|
||||
future: _newVersionLoader,
|
||||
future: _updateChecker,
|
||||
builder: (context, snapshot) {
|
||||
final newVersion = snapshot.data == true;
|
||||
if (!newVersion) return SizedBox();
|
||||
final newVersionAvailable = snapshot.data == true;
|
||||
if (!newVersionAvailable) return SizedBox();
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
|
@ -46,7 +47,7 @@ class _AboutNewVersionState extends State<AboutNewVersion> {
|
|||
),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
),
|
||||
TextSpan(text: 'New Version Available', style: Constants.titleTextStyle),
|
||||
TextSpan(text: context.l10n.aboutUpdate, style: Constants.titleTextStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -55,25 +56,25 @@ class _AboutNewVersionState extends State<AboutNewVersion> {
|
|||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: 'A new version of Aves is available on '),
|
||||
TextSpan(text: context.l10n.aboutUpdateLinks1),
|
||||
WidgetSpan(
|
||||
child: LinkChip(
|
||||
text: 'Github',
|
||||
text: context.l10n.aboutUpdateGithub,
|
||||
url: 'https://github.com/deckerst/aves/releases',
|
||||
textStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
),
|
||||
TextSpan(text: ' and '),
|
||||
TextSpan(text: context.l10n.aboutUpdateLinks2),
|
||||
WidgetSpan(
|
||||
child: LinkChip(
|
||||
text: 'Google Play',
|
||||
text: context.l10n.aboutUpdateGooglePlay,
|
||||
url: 'https://play.google.com/store/apps/details?id=deckers.thibault.aves',
|
||||
textStyle: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
),
|
||||
TextSpan(text: '.'),
|
||||
TextSpan(text: context.l10n.aboutUpdateLinks3),
|
||||
],
|
||||
),
|
||||
),
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:aves/main.dart';
|
||||
import 'package:aves/model/actions/collection_actions.dart';
|
||||
import 'package:aves/model/actions/entry_actions.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
|
@ -15,6 +16,7 @@ import 'package:aves/widgets/collection/filter_bar.dart';
|
|||
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
||||
import 'package:aves/widgets/common/app_bar_title.dart';
|
||||
import 'package:aves/widgets/common/basic/menu_row.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/search/search_button.dart';
|
||||
|
@ -23,7 +25,6 @@ import 'package:aves/widgets/stats/stats.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
|
||||
class CollectionAppBar extends StatefulWidget {
|
||||
|
@ -141,7 +142,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
Widget _buildAppBarTitle() {
|
||||
if (collection.isBrowsing) {
|
||||
Widget title = Text(
|
||||
AvesApp.mode == AppMode.pick ? 'Pick' : 'Collection',
|
||||
AvesApp.mode == AppMode.pick ? context.l10n.collectionPickPageTitle : context.l10n.collectionPageTitle,
|
||||
key: Key('appbar-title'),
|
||||
);
|
||||
if (AvesApp.mode == AppMode.main) {
|
||||
|
@ -150,7 +151,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
source: source,
|
||||
);
|
||||
}
|
||||
return TappableAppBarTitle(
|
||||
return InteractiveAppBarTitle(
|
||||
onTap: _goToSearch,
|
||||
child: title,
|
||||
);
|
||||
|
@ -159,7 +160,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
animation: collection.selectionChangeNotifier,
|
||||
builder: (context, child) {
|
||||
final count = collection.selection.length;
|
||||
return Text(Intl.plural(count, zero: 'Select items', one: '$count item', other: '$count items'));
|
||||
return Text(context.l10n.collectionSelectionPageTitle(count));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -180,7 +181,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
return IconButton(
|
||||
icon: Icon(action.getIcon()),
|
||||
onPressed: collection.selection.isEmpty ? null : () => _actionDelegate.onEntryActionSelected(context, action),
|
||||
tooltip: action.getText(),
|
||||
tooltip: action.getText(context),
|
||||
);
|
||||
},
|
||||
)),
|
||||
|
@ -197,35 +198,30 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
PopupMenuItem(
|
||||
key: Key('menu-sort'),
|
||||
value: CollectionAction.sort,
|
||||
child: MenuRow(text: 'Sort…', icon: AIcons.sort),
|
||||
child: MenuRow(text: context.l10n.menuActionSort, icon: AIcons.sort),
|
||||
),
|
||||
if (collection.sortFactor == EntrySortFactor.date)
|
||||
PopupMenuItem(
|
||||
key: Key('menu-group'),
|
||||
value: CollectionAction.group,
|
||||
child: MenuRow(text: 'Group…', icon: AIcons.group),
|
||||
child: MenuRow(text: context.l10n.menuActionGroup, icon: AIcons.group),
|
||||
),
|
||||
if (collection.isBrowsing) ...[
|
||||
if (kDebugMode)
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.refresh,
|
||||
child: MenuRow(text: 'Refresh', icon: AIcons.refresh),
|
||||
),
|
||||
if (AvesApp.mode == AppMode.main)
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.select,
|
||||
enabled: isNotEmpty,
|
||||
child: MenuRow(text: 'Select', icon: AIcons.select),
|
||||
child: MenuRow(text: context.l10n.collectionActionSelect, icon: AIcons.select),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.stats,
|
||||
enabled: isNotEmpty,
|
||||
child: MenuRow(text: 'Stats', icon: AIcons.stats),
|
||||
child: MenuRow(text: context.l10n.menuActionStats, icon: AIcons.stats),
|
||||
),
|
||||
if (AvesApp.mode == AppMode.main && canAddShortcuts)
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.addShortcut,
|
||||
child: MenuRow(text: 'Add shortcut…', icon: AIcons.addShortcut),
|
||||
child: MenuRow(text: context.l10n.collectionActionAddShortcut, icon: AIcons.addShortcut),
|
||||
),
|
||||
],
|
||||
if (collection.isSelecting) ...[
|
||||
|
@ -233,28 +229,28 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
PopupMenuItem(
|
||||
value: CollectionAction.copy,
|
||||
enabled: hasSelection,
|
||||
child: MenuRow(text: 'Copy to album'),
|
||||
child: MenuRow(text: context.l10n.collectionActionCopy),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.move,
|
||||
enabled: hasSelection,
|
||||
child: MenuRow(text: 'Move to album'),
|
||||
child: MenuRow(text: context.l10n.collectionActionMove),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.refreshMetadata,
|
||||
enabled: hasSelection,
|
||||
child: MenuRow(text: 'Refresh metadata'),
|
||||
child: MenuRow(text: context.l10n.collectionActionRefreshMetadata),
|
||||
),
|
||||
PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.selectAll,
|
||||
enabled: collection.selection.length < collection.entryCount,
|
||||
child: MenuRow(text: 'Select all'),
|
||||
child: MenuRow(text: context.l10n.collectionActionSelectAll),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: CollectionAction.selectNone,
|
||||
enabled: hasSelection,
|
||||
child: MenuRow(text: 'Select none'),
|
||||
child: MenuRow(text: context.l10n.collectionActionSelectNone),
|
||||
),
|
||||
]
|
||||
];
|
||||
|
@ -289,9 +285,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
case CollectionAction.refreshMetadata:
|
||||
_actionDelegate.onCollectionActionSelected(context, action);
|
||||
break;
|
||||
case CollectionAction.refresh:
|
||||
unawaited(source.refresh());
|
||||
break;
|
||||
case CollectionAction.select:
|
||||
collection.select();
|
||||
break;
|
||||
|
@ -313,12 +306,12 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
builder: (context) => AvesSelectionDialog<EntryGroupFactor>(
|
||||
initialValue: settings.collectionGroupFactor,
|
||||
options: {
|
||||
EntryGroupFactor.album: 'By album',
|
||||
EntryGroupFactor.month: 'By month',
|
||||
EntryGroupFactor.day: 'By day',
|
||||
EntryGroupFactor.none: 'Do not group',
|
||||
EntryGroupFactor.album: context.l10n.collectionGroupAlbum,
|
||||
EntryGroupFactor.month: context.l10n.collectionGroupMonth,
|
||||
EntryGroupFactor.day: context.l10n.collectionGroupDay,
|
||||
EntryGroupFactor.none: context.l10n.collectionGroupNone,
|
||||
},
|
||||
title: 'Group',
|
||||
title: context.l10n.collectionGroupTitle,
|
||||
),
|
||||
);
|
||||
if (value != null) {
|
||||
|
@ -332,11 +325,11 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
builder: (context) => AvesSelectionDialog<EntrySortFactor>(
|
||||
initialValue: settings.collectionSortFactor,
|
||||
options: {
|
||||
EntrySortFactor.date: 'By date',
|
||||
EntrySortFactor.size: 'By size',
|
||||
EntrySortFactor.name: 'By album & file name',
|
||||
EntrySortFactor.date: context.l10n.collectionSortDate,
|
||||
EntrySortFactor.size: context.l10n.collectionSortSize,
|
||||
EntrySortFactor.name: context.l10n.collectionSortName,
|
||||
},
|
||||
title: 'Sort',
|
||||
title: context.l10n.collectionSortTitle,
|
||||
),
|
||||
);
|
||||
if (value != null) {
|
||||
|
@ -348,14 +341,24 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
}
|
||||
|
||||
Future<void> _showShortcutDialog(BuildContext context) async {
|
||||
final filters = collection.filters;
|
||||
var defaultName;
|
||||
if (filters.isEmpty) {
|
||||
defaultName = context.l10n.collectionPageTitle;
|
||||
} else {
|
||||
final sortedFilters = List<CollectionFilter>.from(filters)..sort();
|
||||
defaultName = sortedFilters.first.getLabel(context);
|
||||
}
|
||||
final name = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AddShortcutDialog(collection.filters),
|
||||
builder: (context) {
|
||||
return AddShortcutDialog(defaultName: defaultName);
|
||||
},
|
||||
);
|
||||
if (name == null || name.isEmpty) return;
|
||||
|
||||
final iconEntry = collection.sortedEntries.isNotEmpty ? collection.sortedEntries.first : null;
|
||||
unawaited(AppShortcutService.pin(name, iconEntry, collection.filters));
|
||||
unawaited(AppShortcutService.pin(name, iconEntry, filters));
|
||||
}
|
||||
|
||||
void _goToSearch() {
|
||||
|
|
|
@ -14,12 +14,12 @@ import 'package:aves/utils/android_file_utils.dart';
|
|||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||
final CollectionLens collection;
|
||||
|
@ -112,10 +112,10 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final movedCount = movedOps.length;
|
||||
if (movedCount < todoCount) {
|
||||
final count = todoCount - movedCount;
|
||||
showFeedback(context, 'Failed to ${copy ? 'copy' : 'move'} ${Intl.plural(count, one: '$count item', other: '$count items')}');
|
||||
showFeedback(context, copy ? context.l10n.collectionCopyFailureFeedback(count) : context.l10n.collectionMoveFailureFeedback(count));
|
||||
} else {
|
||||
final count = movedCount;
|
||||
showFeedback(context, '${copy ? 'Copied' : 'Moved'} ${Intl.plural(count, one: '$count item', other: '$count items')}');
|
||||
showFeedback(context, copy ? context.l10n.collectionCopySuccessFeedback(count) : context.l10n.collectionMoveSuccessFeedback(count));
|
||||
}
|
||||
await source.updateAfterMove(
|
||||
todoEntries: todoEntries,
|
||||
|
@ -138,15 +138,15 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
builder: (context) {
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
content: Text('Are you sure you want to delete ${Intl.plural(count, one: 'this item', other: 'these $count items')}?'),
|
||||
content: Text(context.l10n.deleteEntriesConfirmationDialogMessage(count)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text('Delete'.toUpperCase()),
|
||||
child: Text(context.l10n.deleteButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -167,7 +167,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final deletedCount = deletedUris.length;
|
||||
if (deletedCount < selectionCount) {
|
||||
final count = selectionCount - deletedCount;
|
||||
showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}');
|
||||
showFeedback(context, context.l10n.collectionDeleteFailureFeedback(count));
|
||||
}
|
||||
source.removeEntries(deletedUris);
|
||||
collection.browse();
|
||||
|
|
|
@ -14,7 +14,7 @@ class FilterBar extends StatefulWidget implements PreferredSizeWidget {
|
|||
Key key,
|
||||
@required Set<CollectionFilter> filters,
|
||||
@required this.onPressed,
|
||||
}) : filters = List.from(filters)..sort(),
|
||||
}) : filters = List<CollectionFilter>.from(filters)..sort(),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
|
|
|
@ -9,12 +9,11 @@ import 'package:flutter/material.dart';
|
|||
class AlbumSectionHeader extends StatelessWidget {
|
||||
final String directory, albumName;
|
||||
|
||||
AlbumSectionHeader({
|
||||
const AlbumSectionHeader({
|
||||
Key key,
|
||||
@required CollectionSource source,
|
||||
@required this.directory,
|
||||
}) : albumName = source.getUniqueAlbumName(directory),
|
||||
super(key: key);
|
||||
@required this.albumName,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -47,7 +46,7 @@ class AlbumSectionHeader extends StatelessWidget {
|
|||
return SectionHeader.getPreferredHeight(
|
||||
context: context,
|
||||
maxWidth: maxWidth,
|
||||
title: source.getUniqueAlbumName(directory),
|
||||
title: source.getUniqueAlbumName(context, directory),
|
||||
hasLeading: androidFileUtils.getAlbumType(directory) != AlbumType.regular,
|
||||
hasTrailing: androidFileUtils.isOnRemovableStorage(directory),
|
||||
);
|
||||
|
|
|
@ -23,7 +23,7 @@ class CollectionSectionHeader extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final header = _buildHeader();
|
||||
final header = _buildHeader(context);
|
||||
return header != null
|
||||
? SizedBox(
|
||||
height: height,
|
||||
|
@ -32,18 +32,12 @@ class CollectionSectionHeader extends StatelessWidget {
|
|||
: SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
Widget _buildAlbumHeader() => AlbumSectionHeader(
|
||||
key: ValueKey(sectionKey),
|
||||
source: collection.source,
|
||||
directory: (sectionKey as EntryAlbumSectionKey).directory,
|
||||
);
|
||||
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
switch (collection.sortFactor) {
|
||||
case EntrySortFactor.date:
|
||||
switch (collection.groupFactor) {
|
||||
case EntryGroupFactor.album:
|
||||
return _buildAlbumHeader();
|
||||
return _buildAlbumHeader(context);
|
||||
case EntryGroupFactor.month:
|
||||
return MonthSectionHeader(key: ValueKey(sectionKey), date: (sectionKey as EntryDateSectionKey).date);
|
||||
case EntryGroupFactor.day:
|
||||
|
@ -53,13 +47,23 @@ class CollectionSectionHeader extends StatelessWidget {
|
|||
}
|
||||
break;
|
||||
case EntrySortFactor.name:
|
||||
return _buildAlbumHeader();
|
||||
return _buildAlbumHeader(context);
|
||||
case EntrySortFactor.size:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget _buildAlbumHeader(BuildContext context) {
|
||||
final source = collection.source;
|
||||
final directory = (sectionKey as EntryAlbumSectionKey).directory;
|
||||
return AlbumSectionHeader(
|
||||
key: ValueKey(sectionKey),
|
||||
directory: directory,
|
||||
albumName: source.getUniqueAlbumName(context, directory),
|
||||
);
|
||||
}
|
||||
|
||||
static double getPreferredHeight(BuildContext context, double maxWidth, CollectionSource source, SectionKey sectionKey) {
|
||||
var headerExtent = 0.0;
|
||||
if (sectionKey is EntryAlbumSectionKey) {
|
||||
|
|
|
@ -1,73 +1,79 @@
|
|||
import 'package:aves/model/source/section_keys.dart';
|
||||
import 'package:aves/utils/time_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/grid/header.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DaySectionHeader extends StatelessWidget {
|
||||
final DateTime date;
|
||||
final String text;
|
||||
|
||||
DaySectionHeader({
|
||||
const DaySectionHeader({
|
||||
Key key,
|
||||
@required this.date,
|
||||
}) : text = _formatDate(date),
|
||||
super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
// Examples (en_US):
|
||||
// `MMMMd`: `April 15`
|
||||
// `yMMMMd`: `April 15, 2020`
|
||||
// `MMMEd`: `Wed, Apr 15`
|
||||
// `yMMMEd`: `Wed, Apr 15, 2020`
|
||||
// `MMMMEEEEd`: `Wednesday, April 15`
|
||||
// `yMMMMEEEEd`: `Wednesday, April 15, 2020`
|
||||
// `MEd`: `Wed, 4/15`
|
||||
// `yMEd`: `Wed, 4/15/2020`
|
||||
static DateFormat md = DateFormat.MMMMd();
|
||||
static DateFormat ymd = DateFormat.yMMMMd();
|
||||
static DateFormat day = DateFormat.E();
|
||||
// `MMMMd`: `April 15`
|
||||
// `yMMMMd`: `April 15, 2020`
|
||||
// `MMMEd`: `Wed, Apr 15`
|
||||
// `yMMMEd`: `Wed, Apr 15, 2020`
|
||||
// `MMMMEEEEd`: `Wednesday, April 15`
|
||||
// `yMMMMEEEEd`: `Wednesday, April 15, 2020`
|
||||
// `MEd`: `Wed, 4/15`
|
||||
// `yMEd`: `Wed, 4/15/2020`
|
||||
|
||||
static String _formatDate(DateTime date) {
|
||||
if (date.isToday) return 'Today';
|
||||
if (date.isYesterday) return 'Yesterday';
|
||||
if (date.isThisYear) return '${md.format(date)} (${day.format(date)})';
|
||||
return '${ymd.format(date)} (${day.format(date)})';
|
||||
// Examples (ko):
|
||||
// `MMMMd`: `1월 26일`
|
||||
// `yMMMMd`: `2021년 1월 26일`
|
||||
// `MMMEd`: `1월 26일 (화)`
|
||||
// `yMMMEd`: `2021년 1월 26일 (화)`
|
||||
// `MMMMEEEEd`: `1월 26일 화요일`
|
||||
// `yMMMMEEEEd`: `2021년 1월 26일 화요일`
|
||||
// `MEd`: `1. 26. (화)`
|
||||
// `yMEd`: `2021. 1. 26. (화)`
|
||||
|
||||
static String _formatDate(BuildContext context, DateTime date) {
|
||||
final l10n = context.l10n;
|
||||
if (date == null) return l10n.sectionUnknown;
|
||||
if (date.isToday) return l10n.dateToday;
|
||||
if (date.isYesterday) return l10n.dateYesterday;
|
||||
final locale = l10n.localeName;
|
||||
if (date.isThisYear) return '${DateFormat.MMMMd(locale).format(date)} (${DateFormat.E(locale).format(date)})';
|
||||
return '${DateFormat.yMMMMd(locale).format(date)} (${DateFormat.E(locale).format(date)})';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SectionHeader(
|
||||
sectionKey: EntryDateSectionKey(date),
|
||||
title: text,
|
||||
title: _formatDate(context, date),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MonthSectionHeader extends StatelessWidget {
|
||||
final DateTime date;
|
||||
final String text;
|
||||
|
||||
MonthSectionHeader({
|
||||
const MonthSectionHeader({
|
||||
Key key,
|
||||
@required this.date,
|
||||
}) : text = _formatDate(date),
|
||||
super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
static DateFormat m = DateFormat.MMMM();
|
||||
static DateFormat ym = DateFormat.yMMMM();
|
||||
|
||||
static String _formatDate(DateTime date) {
|
||||
if (date == null) return 'Unknown';
|
||||
if (date.isThisMonth) return 'This month';
|
||||
if (date.isThisYear) return m.format(date);
|
||||
return ym.format(date);
|
||||
static String _formatDate(BuildContext context, DateTime date) {
|
||||
final l10n = context.l10n;
|
||||
if (date == null) return l10n.sectionUnknown;
|
||||
if (date.isThisMonth) return l10n.dateThisMonth;
|
||||
final locale = l10n.localeName;
|
||||
if (date.isThisYear) return DateFormat.MMMM(locale).format(date);
|
||||
return DateFormat.yMMMM(locale).format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SectionHeader(
|
||||
sectionKey: EntryDateSectionKey(date),
|
||||
title: text,
|
||||
title: _formatDate(context, date),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/highlight.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/model/entry_images.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/collection/thumbnail/error.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/fx/transition_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -79,7 +80,7 @@ class _RasterImageThumbnailState extends State<RasterImageThumbnail> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!entry.canDecode) {
|
||||
return _buildError(context, '${entry.mimeType} not supported', null);
|
||||
return _buildError(context, context.l10n.errorUnsupportedMimeType(entry.mimeType), null);
|
||||
}
|
||||
|
||||
final fastImage = Image(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/image_providers/uri_picture_provider.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/settings/entry_background.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
|
@ -6,12 +6,11 @@ import 'package:aves/model/filters/favourite.dart';
|
|||
import 'package:aves/model/filters/mime.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/app_bar.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/collection/grid/section_layout.dart';
|
||||
import 'package:aves/widgets/collection/grid/selector.dart';
|
||||
import 'package:aves/widgets/collection/grid/thumbnail.dart';
|
||||
|
@ -23,6 +22,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
|
|||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||
import 'package:aves/widgets/common/grid/section_layout.dart';
|
||||
import 'package:aves/widgets/common/grid/sliver.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/common/identity/scroll_thumb.dart';
|
||||
import 'package:aves/widgets/common/scaling.dart';
|
||||
import 'package:aves/widgets/common/tile_extent_manager.dart';
|
||||
|
@ -265,18 +265,18 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
|
|||
if (collection.filters.any((filter) => filter is FavouriteFilter)) {
|
||||
return EmptyContent(
|
||||
icon: AIcons.favourite,
|
||||
text: 'No favourites',
|
||||
text: context.l10n.collectionEmptyFavourites,
|
||||
);
|
||||
}
|
||||
if (collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.anyVideo)) {
|
||||
return EmptyContent(
|
||||
icon: AIcons.video,
|
||||
text: 'No videos',
|
||||
text: context.l10n.collectionEmptyVideos,
|
||||
);
|
||||
}
|
||||
return EmptyContent(
|
||||
icon: AIcons.image,
|
||||
text: 'No images',
|
||||
text: context.l10n.collectionEmptyImages,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/services/android_file_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -26,18 +27,20 @@ mixin PermissionAwareMixin {
|
|||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final directory = dir.relativeDir.isEmpty ? context.l10n.rootDirectoryDescription : context.l10n.otherDirectoryDescription(dir.relativeDir);
|
||||
final volume = dir.getVolumeDescription(context);
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
title: 'Storage Volume Access',
|
||||
content: Text('Please select the ${dir.directoryDescription} directory of “${dir.volumeDescription}” in the next screen, so that this app can access it and complete your request.'),
|
||||
title: context.l10n.storageAccessDialogTitle,
|
||||
content: Text(context.l10n.storageVolumeAccessDialogMessage(directory, volume)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text('OK'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -58,14 +61,16 @@ mixin PermissionAwareMixin {
|
|||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final directory = dir.relativeDir.isEmpty ? context.l10n.rootDirectoryDescription : context.l10n.otherDirectoryDescription(dir.relativeDir);
|
||||
final volume = dir.getVolumeDescription(context);
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
title: 'Restricted Access',
|
||||
content: Text('This app is not allowed to modify files in the ${dir.directoryDescription} directory of “${dir.volumeDescription}”.\n\nPlease use a pre-installed file manager or gallery app to move the items to another directory.'),
|
||||
title: context.l10n.restrictedAccessDialogTitle,
|
||||
content: Text(context.l10n.restrictedAccessDialogMessage(directory, volume)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('OK'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/services/android_file_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -43,14 +44,17 @@ mixin SizeAwareMixin {
|
|||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final neededSize = formatFilesize(needed);
|
||||
final freeSize = formatFilesize(free);
|
||||
final volume = destinationVolume.getDescription(context);
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
title: 'Not Enough Space',
|
||||
content: Text('This operation needs ${formatFilesize(needed)} of free space on “${destinationVolume.description}” to complete, but there is only ${formatFilesize(free)} left.'),
|
||||
title: context.l10n.notEnoughSpaceDialogTitle,
|
||||
content: Text(context.l10n.notEnoughSpaceDialogMessage(neededSize, freeSize, volume)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('OK'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SourceStateAwareAppBarTitle extends StatelessWidget {
|
||||
|
@ -54,13 +56,13 @@ class SourceStateSubtitle extends StatelessWidget {
|
|||
String subtitle;
|
||||
switch (source.stateNotifier.value) {
|
||||
case SourceState.loading:
|
||||
subtitle = 'Loading';
|
||||
subtitle = context.l10n.sourceStateLoading;
|
||||
break;
|
||||
case SourceState.cataloguing:
|
||||
subtitle = 'Cataloguing';
|
||||
subtitle = context.l10n.sourceStateCataloguing;
|
||||
break;
|
||||
case SourceState.locating:
|
||||
subtitle = 'Locating';
|
||||
subtitle = context.l10n.sourceStateLocating;
|
||||
break;
|
||||
case SourceState.ready:
|
||||
default:
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class TappableAppBarTitle extends StatelessWidget {
|
||||
class InteractiveAppBarTitle extends StatelessWidget {
|
||||
final GestureTapCallback onTap;
|
||||
final Widget child;
|
||||
|
||||
const TappableAppBarTitle({
|
||||
const InteractiveAppBarTitle({
|
||||
this.onTap,
|
||||
@required this.child,
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/debouncer.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -34,7 +35,7 @@ class _QueryBarState extends State<QueryBar> {
|
|||
_controller.clear();
|
||||
filterNotifier.value = '';
|
||||
},
|
||||
tooltip: 'Clear',
|
||||
tooltip: context.l10n.clearTooltip,
|
||||
);
|
||||
|
||||
return Row(
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:overlay_support/overlay_support.dart';
|
||||
|
@ -37,7 +38,7 @@ class _DoubleBackPopScopeState extends State<DoubleBackPopScope> with FeedbackMi
|
|||
_stopBackTimer();
|
||||
_backTimer = Timer(Durations.doubleBackTimerDelay, () => _backOnce = false);
|
||||
toast(
|
||||
'Tap “back” again to exit.',
|
||||
context.l10n.doubleBackExitMessage,
|
||||
duration: Durations.doubleBackTimerDelay,
|
||||
);
|
||||
return SynchronousFuture(false);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
extension ExtraContext on BuildContext {
|
||||
String get currentRouteName => ModalRoute.of(this)?.settings?.name;
|
||||
|
||||
AppLocalizations get l10n => AppLocalizations.of(this);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/source/section_keys.dart';
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -161,7 +162,7 @@ class _SectionSelectableLeading extends StatelessWidget {
|
|||
alignment: AlignmentDirectional.topStart,
|
||||
icon: Icon(selected ? AIcons.selected : AIcons.unselected),
|
||||
onPressed: onPressed,
|
||||
tooltip: selected ? 'Deselect section' : 'Select section',
|
||||
tooltip: selected ? context.l10n.collectionDeselectSectionTooltip : context.l10n.collectionSelectSectionTooltip,
|
||||
constraints: BoxConstraints(
|
||||
minHeight: leadingDimension,
|
||||
minWidth: leadingDimension,
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:provider/provider.dart';
|
|||
// Use a `SliverList` instead of multiple `SliverGrid` because having one `SliverGrid` per section does not scale up.
|
||||
// With the multiple `SliverGrid` solution, thumbnails at the beginning of each sections are built even though they are offscreen
|
||||
// because of `RenderSliverMultiBoxAdaptor.addInitialChild` called by `RenderSliverGrid.performLayout` (line 547), as of Flutter v1.17.0.
|
||||
// cf https://github.com/flutter/flutter/issues/49027
|
||||
class SectionedListSliver<T> extends StatelessWidget {
|
||||
const SectionedListSliver();
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class AvesFilterChip extends StatefulWidget {
|
|||
items: actions
|
||||
.map((action) => PopupMenuItem(
|
||||
value: action,
|
||||
child: MenuRow(text: action.getText(), icon: action.getIcon()),
|
||||
child: MenuRow(text: action.getText(context), icon: action.getIcon()),
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
|
@ -103,10 +103,15 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initColorLoader();
|
||||
_tapped = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_initColorLoader();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AvesFilterChip oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
@ -146,7 +151,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
],
|
||||
Flexible(
|
||||
child: Text(
|
||||
filter.label,
|
||||
filter.getLabel(context),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
|
@ -203,7 +208,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
child: widget.background,
|
||||
),
|
||||
Tooltip(
|
||||
message: filter.tooltip,
|
||||
message: filter.getTooltip(context),
|
||||
preferBelow: false,
|
||||
child: Material(
|
||||
color: hasBackground ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
|
||||
|
|
|
@ -40,6 +40,8 @@ class DebugSettingsSection extends StatelessWidget {
|
|||
'pinnedFilters': toMultiline(settings.pinnedFilters),
|
||||
'searchHistory': toMultiline(settings.searchHistory),
|
||||
'lastVersionCheckDate': '${settings.lastVersionCheckDate}',
|
||||
'locale': '${settings.locale}',
|
||||
'systemLocale': '${WidgetsBinding.instance.window.locale}',
|
||||
}),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -39,7 +39,7 @@ class _DebugStorageSectionState extends State<DebugStorageSection> with Automati
|
|||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: InfoRowGroup({
|
||||
'description': '${v.description}',
|
||||
'description': '${v.getDescription(context)}',
|
||||
'isPrimary': '${v.isPrimary}',
|
||||
'isRemovable': '${v.isRemovable}',
|
||||
'state': '${v.state}',
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'aves_dialog.dart';
|
||||
|
||||
class AddShortcutDialog extends StatefulWidget {
|
||||
final Set<CollectionFilter> filters;
|
||||
final String defaultName;
|
||||
|
||||
const AddShortcutDialog(this.filters);
|
||||
const AddShortcutDialog({
|
||||
@required this.defaultName,
|
||||
});
|
||||
|
||||
@override
|
||||
_AddShortcutDialogState createState() => _AddShortcutDialogState();
|
||||
|
@ -19,12 +21,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final filters = List.from(widget.filters)..sort();
|
||||
if (filters.isEmpty) {
|
||||
_nameController.text = 'Collection';
|
||||
} else {
|
||||
_nameController.text = filters.first.label;
|
||||
}
|
||||
_nameController.text = widget.defaultName;
|
||||
_validate();
|
||||
}
|
||||
|
||||
|
@ -41,7 +38,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
|
|||
content: TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Shortcut label',
|
||||
labelText: context.l10n.addShortcutDialogLabel,
|
||||
),
|
||||
autofocus: true,
|
||||
maxLength: 25,
|
||||
|
@ -51,14 +48,14 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
return TextButton(
|
||||
onPressed: isValid ? () => _submit(context) : null,
|
||||
child: Text('Add'.toUpperCase()),
|
||||
child: Text(context.l10n.addShortcutButtonLabel),
|
||||
);
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
|
@ -92,12 +93,12 @@ void showNoMatchingAppDialog(BuildContext context) {
|
|||
builder: (context) {
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
title: 'No Matching App',
|
||||
content: Text('There are no apps that can handle this.'),
|
||||
title: context.l10n.noMatchingAppDialogTitle,
|
||||
content: Text(context.l10n.noMatchingAppDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('OK'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -23,7 +23,7 @@ class AvesSelectionDialog<T> extends StatefulWidget {
|
|||
_AvesSelectionDialogState<T> createState() => _AvesSelectionDialogState<T>();
|
||||
}
|
||||
|
||||
class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog> {
|
||||
class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog<T>> {
|
||||
T _selectedValue;
|
||||
|
||||
@override
|
||||
|
@ -41,13 +41,14 @@ class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioListTile(T value, String title) {
|
||||
final subtitle = widget.optionSubtitleBuilder?.call(value);
|
||||
return ReselectableRadioListTile<T>(
|
||||
key: Key(value.toString()),
|
||||
value: value,
|
||||
|
@ -64,9 +65,9 @@ class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog> {
|
|||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
subtitle: widget.optionSubtitleBuilder != null
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
widget.optionSubtitleBuilder(value),
|
||||
subtitle,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -50,17 +51,17 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
|||
volumeTiles.addAll([
|
||||
Padding(
|
||||
padding: AvesDialog.contentHorizontalPadding + EdgeInsets.only(top: 20),
|
||||
child: Text('Storage:'),
|
||||
child: Text(context.l10n.newAlbumDialogStorageLabel),
|
||||
),
|
||||
...primaryVolumes.map(_buildVolumeTile),
|
||||
...otherVolumes.map(_buildVolumeTile),
|
||||
...primaryVolumes.map((volume) => _buildVolumeTile(context, volume)),
|
||||
...otherVolumes.map((volume) => _buildVolumeTile(context, volume)),
|
||||
SizedBox(height: 8),
|
||||
]);
|
||||
}
|
||||
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
title: 'New Album',
|
||||
title: context.l10n.newAlbumDialogTitle,
|
||||
scrollController: _scrollController,
|
||||
scrollableContent: [
|
||||
...volumeTiles,
|
||||
|
@ -73,8 +74,8 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
|||
controller: _nameController,
|
||||
focusNode: _nameFieldFocusNode,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Album name',
|
||||
helperText: exists ? 'Directory already exists' : '',
|
||||
labelText: context.l10n.newAlbumDialogNameLabel,
|
||||
helperText: exists ? context.l10n.newAlbumDialogNameLabelAlreadyExistsHelper : '',
|
||||
),
|
||||
autofocus: _allVolumes.length == 1,
|
||||
onChanged: (_) => _validate(),
|
||||
|
@ -86,14 +87,14 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
return TextButton(
|
||||
onPressed: isValid ? () => _submit(context) : null,
|
||||
child: Text('Create'.toUpperCase()),
|
||||
child: Text(context.l10n.createAlbumButtonLabel),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -101,7 +102,7 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildVolumeTile(StorageVolume volume) => RadioListTile<StorageVolume>(
|
||||
Widget _buildVolumeTile(BuildContext context, StorageVolume volume) => RadioListTile<StorageVolume>(
|
||||
value: volume,
|
||||
groupValue: _selectedVolume,
|
||||
onChanged: (volume) {
|
||||
|
@ -110,7 +111,7 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
|||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
volume.description,
|
||||
volume.getDescription(context),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
|
@ -46,8 +47,8 @@ class _RenameAlbumDialogState extends State<RenameAlbumDialog> {
|
|||
return TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'New name',
|
||||
helperText: exists ? 'Directory already exists' : '',
|
||||
labelText: context.l10n.renameAlbumDialogLabel,
|
||||
helperText: exists ? context.l10n.renameAlbumDialogLabelAlreadyExistsHelper : '',
|
||||
),
|
||||
autofocus: true,
|
||||
onChanged: (_) => _validate(),
|
||||
|
@ -57,14 +58,14 @@ class _RenameAlbumDialogState extends State<RenameAlbumDialog> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
return TextButton(
|
||||
onPressed: isValid ? () => _submit(context) : null,
|
||||
child: Text('Apply'.toUpperCase()),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
);
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
|
@ -41,7 +42,7 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
|
|||
content: TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'New name',
|
||||
labelText: context.l10n.renameEntryDialogLabel,
|
||||
suffixText: entry.extension,
|
||||
),
|
||||
autofocus: true,
|
||||
|
@ -51,14 +52,14 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
return TextButton(
|
||||
onPressed: isValid ? () => _submit(context) : null,
|
||||
child: Text('Apply'.toUpperCase()),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
);
|
||||
},
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@ class AlbumTile extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final source = context.read<CollectionSource>();
|
||||
final uniqueName = source.getUniqueAlbumName(album);
|
||||
final uniqueName = source.getUniqueAlbumName(context, album);
|
||||
return CollectionNavTile(
|
||||
leading: IconUtils.getAlbumIcon(
|
||||
context: context,
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:aves/theme/icons.dart';
|
|||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/about/about_page.dart';
|
||||
import 'package:aves/widgets/about/news_badge.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_logo.dart';
|
||||
import 'package:aves/widgets/debug/app_debug_page.dart';
|
||||
|
@ -105,7 +106,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
children: [
|
||||
AvesLogo(size: 64),
|
||||
Text(
|
||||
'Aves',
|
||||
context.l10n.appName,
|
||||
style: TextStyle(
|
||||
fontSize: 44,
|
||||
fontWeight: FontWeight.w300,
|
||||
|
@ -146,25 +147,25 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
|
||||
Widget get allCollectionTile => CollectionNavTile(
|
||||
leading: Icon(AIcons.allCollection),
|
||||
title: 'All collection',
|
||||
title: context.l10n.drawerCollectionAll,
|
||||
filter: null,
|
||||
);
|
||||
|
||||
Widget get videoTile => CollectionNavTile(
|
||||
leading: Icon(AIcons.video),
|
||||
title: 'Videos',
|
||||
title: context.l10n.drawerCollectionVideos,
|
||||
filter: MimeFilter(MimeTypes.anyVideo),
|
||||
);
|
||||
|
||||
Widget get favouriteTile => CollectionNavTile(
|
||||
leading: Icon(AIcons.favourite),
|
||||
title: 'Favourites',
|
||||
title: context.l10n.drawerCollectionFavourites,
|
||||
filter: FavouriteFilter(),
|
||||
);
|
||||
|
||||
Widget get albumListTile => NavTile(
|
||||
icon: AIcons.album,
|
||||
title: 'Albums',
|
||||
title: context.l10n.albumPageTitle,
|
||||
trailing: StreamBuilder(
|
||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, _) => Text('${source.rawAlbums.length}'),
|
||||
|
@ -175,7 +176,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
|
||||
Widget get countryListTile => NavTile(
|
||||
icon: AIcons.location,
|
||||
title: 'Countries',
|
||||
title: context.l10n.countryPageTitle,
|
||||
trailing: StreamBuilder(
|
||||
stream: source.eventBus.on<CountriesChangedEvent>(),
|
||||
builder: (context, _) => Text('${source.sortedCountries.length}'),
|
||||
|
@ -186,7 +187,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
|
||||
Widget get tagListTile => NavTile(
|
||||
icon: AIcons.tag,
|
||||
title: 'Tags',
|
||||
title: context.l10n.tagPageTitle,
|
||||
trailing: StreamBuilder(
|
||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||
builder: (context, _) => Text('${source.sortedTags.length}'),
|
||||
|
@ -197,7 +198,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
|
||||
Widget get settingsTile => NavTile(
|
||||
icon: AIcons.settings,
|
||||
title: 'Settings',
|
||||
title: context.l10n.settingsPageTitle,
|
||||
topLevel: false,
|
||||
routeName: SettingsPage.routeName,
|
||||
pageBuilder: (_) => SettingsPage(),
|
||||
|
@ -209,7 +210,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
final newVersion = snapshot.data == true;
|
||||
return NavTile(
|
||||
icon: AIcons.info,
|
||||
title: 'About',
|
||||
title: context.l10n.aboutPageTitle,
|
||||
trailing: newVersion ? AboutNewsBadge() : null,
|
||||
topLevel: false,
|
||||
routeName: AboutPage.routeName,
|
||||
|
|
|
@ -7,10 +7,11 @@ import 'package:aves/model/source/collection_source.dart';
|
|||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
||||
import 'package:aves/widgets/common/basic/menu_row.dart';
|
||||
import 'package:aves/widgets/common/basic/query_bar.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/dialogs/create_album_dialog.dart';
|
||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||
|
@ -58,7 +59,7 @@ class _AlbumPickPageState extends State<AlbumPickPage> {
|
|||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterGridPage<AlbumFilter>(
|
||||
appBar: appBar,
|
||||
filterSections: AlbumListPage.getAlbumEntries(source),
|
||||
filterSections: AlbumListPage.getAlbumEntries(context, source),
|
||||
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
||||
applyQuery: (filters, query) {
|
||||
if (query == null || query.isEmpty) return filters;
|
||||
|
@ -68,7 +69,7 @@ class _AlbumPickPageState extends State<AlbumPickPage> {
|
|||
queryNotifier: _queryNotifier,
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
text: 'No albums',
|
||||
text: context.l10n.albumEmpty,
|
||||
),
|
||||
settingsRouteKey: AlbumListPage.routeName,
|
||||
appBarHeight: AlbumPickAppBar.preferredHeight,
|
||||
|
@ -100,11 +101,11 @@ class AlbumPickAppBar extends StatelessWidget {
|
|||
String title() {
|
||||
switch (moveType) {
|
||||
case MoveType.copy:
|
||||
return 'Copy to Album';
|
||||
return context.l10n.albumPickPageTitleCopy;
|
||||
case MoveType.export:
|
||||
return 'Export to Album';
|
||||
return context.l10n.albumPickPageTitleExport;
|
||||
case MoveType.move:
|
||||
return 'Move to Album';
|
||||
return context.l10n.albumPickPageTitleMove;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -131,22 +132,26 @@ class AlbumPickAppBar extends StatelessWidget {
|
|||
Navigator.pop<String>(context, newAlbum);
|
||||
}
|
||||
},
|
||||
tooltip: 'Create album',
|
||||
tooltip: context.l10n.createAlbumTooltip,
|
||||
),
|
||||
PopupMenuButton<ChipSetAction>(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
value: ChipSetAction.sort,
|
||||
child: MenuRow(text: 'Sort…', icon: AIcons.sort),
|
||||
child: MenuRow(text: context.l10n.menuActionSort, icon: AIcons.sort),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: ChipSetAction.group,
|
||||
child: MenuRow(text: 'Group…', icon: AIcons.group),
|
||||
child: MenuRow(text: context.l10n.menuActionGroup, icon: AIcons.group),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (action) {
|
||||
// remove focus, if any, to prevent the keyboard from showing up
|
||||
// after the user is done with the popup menu
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
Future.delayed(Durations.popupMenuAnimation * timeDilation, () => actionDelegate.onActionSelected(context, action));
|
||||
},
|
||||
|
|
|
@ -7,7 +7,8 @@ import 'package:aves/model/source/collection_source.dart';
|
|||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
|
||||
|
@ -32,7 +33,7 @@ class AlbumListPage extends StatelessWidget {
|
|||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage<AlbumFilter>(
|
||||
source: source,
|
||||
title: 'Albums',
|
||||
title: context.l10n.albumPageTitle,
|
||||
groupable: true,
|
||||
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
||||
chipSetActionDelegate: AlbumChipSetActionDelegate(),
|
||||
|
@ -43,10 +44,10 @@ class AlbumListPage extends StatelessWidget {
|
|||
ChipAction.delete,
|
||||
ChipAction.hide,
|
||||
],
|
||||
filterSections: getAlbumEntries(source),
|
||||
filterSections: getAlbumEntries(context, source),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
text: 'No albums',
|
||||
text: context.l10n.albumEmpty,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -57,14 +58,14 @@ class AlbumListPage extends StatelessWidget {
|
|||
|
||||
// common with album selection page to move/copy entries
|
||||
|
||||
static Map<ChipSectionKey, List<FilterGridItem<AlbumFilter>>> getAlbumEntries(CollectionSource source) {
|
||||
final filters = source.rawAlbums.map((album) => AlbumFilter(album, source.getUniqueAlbumName(album))).toSet();
|
||||
static Map<ChipSectionKey, List<FilterGridItem<AlbumFilter>>> getAlbumEntries(BuildContext context, CollectionSource source) {
|
||||
final filters = source.rawAlbums.map((album) => AlbumFilter(album, source.getUniqueAlbumName(context, album))).toSet();
|
||||
|
||||
final sorted = FilterNavigationPage.sort(settings.albumSortFactor, source, filters);
|
||||
return _group(sorted);
|
||||
return _group(context, sorted);
|
||||
}
|
||||
|
||||
static Map<ChipSectionKey, List<FilterGridItem<AlbumFilter>>> _group(Iterable<FilterGridItem<AlbumFilter>> sortedMapEntries) {
|
||||
static Map<ChipSectionKey, List<FilterGridItem<AlbumFilter>>> _group(BuildContext context, Iterable<FilterGridItem<AlbumFilter>> sortedMapEntries) {
|
||||
final pinned = settings.pinnedFilters.whereType<AlbumFilter>();
|
||||
final byPin = groupBy<FilterGridItem<AlbumFilter>, bool>(sortedMapEntries, (e) => pinned.contains(e.filter));
|
||||
final pinnedMapEntries = (byPin[true] ?? []);
|
||||
|
@ -73,25 +74,28 @@ class AlbumListPage extends StatelessWidget {
|
|||
var sections = <ChipSectionKey, List<FilterGridItem<AlbumFilter>>>{};
|
||||
switch (settings.albumGroupFactor) {
|
||||
case AlbumChipGroupFactor.importance:
|
||||
final specialKey = AlbumImportanceSectionKey.special(context);
|
||||
final appsKey = AlbumImportanceSectionKey.apps(context);
|
||||
final regularKey = AlbumImportanceSectionKey.regular(context);
|
||||
sections = groupBy<FilterGridItem<AlbumFilter>, ChipSectionKey>(unpinnedMapEntries, (kv) {
|
||||
switch (androidFileUtils.getAlbumType(kv.filter.album)) {
|
||||
case AlbumType.regular:
|
||||
return AlbumImportanceSectionKey.regular;
|
||||
return regularKey;
|
||||
case AlbumType.app:
|
||||
return AlbumImportanceSectionKey.apps;
|
||||
return appsKey;
|
||||
default:
|
||||
return AlbumImportanceSectionKey.special;
|
||||
return specialKey;
|
||||
}
|
||||
});
|
||||
sections = {
|
||||
AlbumImportanceSectionKey.special: sections[AlbumImportanceSectionKey.special],
|
||||
AlbumImportanceSectionKey.apps: sections[AlbumImportanceSectionKey.apps],
|
||||
AlbumImportanceSectionKey.regular: sections[AlbumImportanceSectionKey.regular],
|
||||
specialKey: sections[specialKey],
|
||||
appsKey: sections[appsKey],
|
||||
regularKey: sections[regularKey],
|
||||
}..removeWhere((key, value) => value == null);
|
||||
break;
|
||||
case AlbumChipGroupFactor.volume:
|
||||
sections = groupBy<FilterGridItem<AlbumFilter>, ChipSectionKey>(unpinnedMapEntries, (kv) {
|
||||
return StorageVolumeSectionKey(androidFileUtils.getStorageVolume(kv.filter.album));
|
||||
return StorageVolumeSectionKey(context, androidFileUtils.getStorageVolume(kv.filter.album));
|
||||
});
|
||||
break;
|
||||
case AlbumChipGroupFactor.none:
|
||||
|
@ -106,7 +110,7 @@ class AlbumListPage extends StatelessWidget {
|
|||
|
||||
if (pinnedMapEntries.isNotEmpty) {
|
||||
sections = Map.fromEntries([
|
||||
MapEntry(AlbumImportanceSectionKey.pinned, pinnedMapEntries),
|
||||
MapEntry(AlbumImportanceSectionKey.pinned(context), pinnedMapEntries),
|
||||
...sections.entries,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ import 'package:aves/services/image_op_events.dart';
|
|||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/rename_album_dialog.dart';
|
||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -52,15 +52,15 @@ class ChipActionDelegate {
|
|||
builder: (context) {
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
content: Text('Matching photos and videos will be hidden from your collection. You can show them again from the “Privacy” settings.\n\nAre you sure you want to hide them?'),
|
||||
content: Text(context.l10n.hideFilterConfirmationDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text('Hide'.toUpperCase()),
|
||||
child: Text(context.l10n.hideButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -116,15 +116,15 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
builder: (context) {
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
content: Text('Are you sure you want to delete this album and its ${Intl.plural(count, one: 'item', other: '$count items')}?'),
|
||||
content: Text(context.l10n.deleteAlbumConfirmationDialogMessage(count)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text('Delete'.toUpperCase()),
|
||||
child: Text(context.l10n.deleteButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -145,7 +145,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
final deletedCount = deletedUris.length;
|
||||
if (deletedCount < selectionCount) {
|
||||
final count = selectionCount - deletedCount;
|
||||
showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}');
|
||||
showFeedback(context, context.l10n.collectionDeleteFailureFeedback(count));
|
||||
}
|
||||
source.removeEntries(deletedUris);
|
||||
source.resumeMonitoring();
|
||||
|
@ -183,9 +183,9 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
final movedCount = movedOps.length;
|
||||
if (movedCount < todoCount) {
|
||||
final count = todoCount - movedCount;
|
||||
showFeedback(context, 'Failed to move ${Intl.plural(count, one: '$count item', other: '$count items')}');
|
||||
showFeedback(context, context.l10n.collectionMoveFailureFeedback(count));
|
||||
} else {
|
||||
showFeedback(context, 'Done!');
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
final pinned = settings.pinnedFilters.contains(filter);
|
||||
await source.updateAfterMove(
|
||||
|
@ -197,7 +197,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
);
|
||||
// repin new album after obsolete album got removed and unpinned
|
||||
if (pinned) {
|
||||
final newFilter = AlbumFilter(destinationAlbum, source.getUniqueAlbumName(destinationAlbum));
|
||||
final newFilter = AlbumFilter(destinationAlbum, source.getUniqueAlbumName(context, destinationAlbum));
|
||||
settings.pinnedFilters = settings.pinnedFilters..add(newFilter);
|
||||
}
|
||||
source.resumeMonitoring();
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:aves/model/actions/chip_actions.dart';
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/stats/stats.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -13,14 +14,10 @@ abstract class ChipSetActionDelegate {
|
|||
set sortFactor(ChipSortFactor factor);
|
||||
|
||||
void onActionSelected(BuildContext context, ChipSetAction action) {
|
||||
final source = context.read<CollectionSource>();
|
||||
switch (action) {
|
||||
case ChipSetAction.sort:
|
||||
_showSortDialog(context);
|
||||
break;
|
||||
case ChipSetAction.refresh:
|
||||
source.refresh();
|
||||
break;
|
||||
case ChipSetAction.stats:
|
||||
_goToStats(context);
|
||||
break;
|
||||
|
@ -35,11 +32,11 @@ abstract class ChipSetActionDelegate {
|
|||
builder: (context) => AvesSelectionDialog<ChipSortFactor>(
|
||||
initialValue: sortFactor,
|
||||
options: {
|
||||
ChipSortFactor.date: 'By date',
|
||||
ChipSortFactor.name: 'By name',
|
||||
ChipSortFactor.count: 'By item count',
|
||||
ChipSortFactor.date: context.l10n.chipSortDate,
|
||||
ChipSortFactor.name: context.l10n.chipSortName,
|
||||
ChipSortFactor.count: context.l10n.chipSortCount,
|
||||
},
|
||||
title: 'Sort',
|
||||
title: context.l10n.chipSortTitle,
|
||||
),
|
||||
);
|
||||
if (factor != null) {
|
||||
|
@ -86,11 +83,11 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate {
|
|||
builder: (context) => AvesSelectionDialog<AlbumChipGroupFactor>(
|
||||
initialValue: settings.albumGroupFactor,
|
||||
options: {
|
||||
AlbumChipGroupFactor.importance: 'By tier',
|
||||
AlbumChipGroupFactor.volume: 'By storage volume',
|
||||
AlbumChipGroupFactor.none: 'Do not group',
|
||||
AlbumChipGroupFactor.importance: context.l10n.albumGroupTier,
|
||||
AlbumChipGroupFactor.volume: context.l10n.albumGroupVolume,
|
||||
AlbumChipGroupFactor.none: context.l10n.albumGroupNone,
|
||||
},
|
||||
title: 'Group',
|
||||
title: context.l10n.albumGroupTitle,
|
||||
),
|
||||
);
|
||||
if (factor != null) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:aves/widgets/collection/collection_page.dart';
|
|||
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
||||
import 'package:aves/widgets/common/app_bar_title.dart';
|
||||
import 'package:aves/widgets/common/basic/menu_row.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||
|
@ -49,7 +50,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
|||
return FilterGridPage<T>(
|
||||
key: ValueKey('filter-grid-page'),
|
||||
appBar: SliverAppBar(
|
||||
title: TappableAppBarTitle(
|
||||
title: InteractiveAppBarTitle(
|
||||
onTap: () => _goToSearch(context),
|
||||
child: SourceStateAwareAppBarTitle(
|
||||
title: Text(title),
|
||||
|
@ -93,7 +94,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
|||
items: chipActionsBuilder(filter)
|
||||
.map((action) => PopupMenuItem(
|
||||
value: action,
|
||||
child: MenuRow(text: action.getText(), icon: action.getIcon()),
|
||||
child: MenuRow(text: action.getText(context), icon: action.getIcon()),
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
|
@ -113,21 +114,16 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
|||
PopupMenuItem(
|
||||
key: Key('menu-sort'),
|
||||
value: ChipSetAction.sort,
|
||||
child: MenuRow(text: 'Sort…', icon: AIcons.sort),
|
||||
child: MenuRow(text: context.l10n.menuActionSort, icon: AIcons.sort),
|
||||
),
|
||||
if (groupable)
|
||||
PopupMenuItem(
|
||||
value: ChipSetAction.group,
|
||||
child: MenuRow(text: 'Group…', icon: AIcons.group),
|
||||
),
|
||||
if (kDebugMode)
|
||||
PopupMenuItem(
|
||||
value: ChipSetAction.refresh,
|
||||
child: MenuRow(text: 'Refresh', icon: AIcons.refresh),
|
||||
child: MenuRow(text: context.l10n.menuActionGroup, icon: AIcons.group),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: ChipSetAction.stats,
|
||||
child: MenuRow(text: 'Stats', icon: AIcons.stats),
|
||||
child: MenuRow(text: context.l10n.menuActionStats, icon: AIcons.stats),
|
||||
),
|
||||
];
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/model/source/section_keys.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -29,12 +30,15 @@ class ChipSectionKey extends SectionKey {
|
|||
class AlbumImportanceSectionKey extends ChipSectionKey {
|
||||
final AlbumImportance importance;
|
||||
|
||||
AlbumImportanceSectionKey._private(this.importance) : super(title: importance.getText());
|
||||
AlbumImportanceSectionKey._private(BuildContext context, this.importance) : super(title: importance.getText(context));
|
||||
|
||||
static AlbumImportanceSectionKey pinned = AlbumImportanceSectionKey._private(AlbumImportance.pinned);
|
||||
static AlbumImportanceSectionKey special = AlbumImportanceSectionKey._private(AlbumImportance.special);
|
||||
static AlbumImportanceSectionKey apps = AlbumImportanceSectionKey._private(AlbumImportance.apps);
|
||||
static AlbumImportanceSectionKey regular = AlbumImportanceSectionKey._private(AlbumImportance.regular);
|
||||
factory AlbumImportanceSectionKey.pinned(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.pinned);
|
||||
|
||||
factory AlbumImportanceSectionKey.special(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.special);
|
||||
|
||||
factory AlbumImportanceSectionKey.apps(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.apps);
|
||||
|
||||
factory AlbumImportanceSectionKey.regular(BuildContext context) => AlbumImportanceSectionKey._private(context, AlbumImportance.regular);
|
||||
|
||||
@override
|
||||
Widget get leading => Icon(importance.getIcon());
|
||||
|
@ -43,16 +47,16 @@ class AlbumImportanceSectionKey extends ChipSectionKey {
|
|||
enum AlbumImportance { pinned, special, apps, regular }
|
||||
|
||||
extension ExtraAlbumImportance on AlbumImportance {
|
||||
String getText() {
|
||||
String getText(BuildContext context) {
|
||||
switch (this) {
|
||||
case AlbumImportance.pinned:
|
||||
return 'Pinned';
|
||||
return context.l10n.albumTierPinned;
|
||||
case AlbumImportance.special:
|
||||
return 'Common';
|
||||
return context.l10n.albumTierSpecial;
|
||||
case AlbumImportance.apps:
|
||||
return 'Apps';
|
||||
return context.l10n.albumTierApps;
|
||||
case AlbumImportance.regular:
|
||||
return 'Others';
|
||||
return context.l10n.albumTierRegular;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -75,7 +79,7 @@ extension ExtraAlbumImportance on AlbumImportance {
|
|||
class StorageVolumeSectionKey extends ChipSectionKey {
|
||||
final StorageVolume volume;
|
||||
|
||||
StorageVolumeSectionKey(this.volume) : super(title: volume?.description ?? 'Unknown');
|
||||
StorageVolumeSectionKey(BuildContext context, this.volume) : super(title: volume?.getDescription(context) ?? context.l10n.sectionUnknown);
|
||||
|
||||
@override
|
||||
Widget get leading => (volume?.isRemovable ?? false) ? Icon(AIcons.removableStorage) : null;
|
||||
|
|
|
@ -6,7 +6,8 @@ import 'package:aves/model/source/collection_source.dart';
|
|||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/location.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
|
||||
|
@ -29,7 +30,7 @@ class CountryListPage extends StatelessWidget {
|
|||
stream: source.eventBus.on<CountriesChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage<LocationFilter>(
|
||||
source: source,
|
||||
title: 'Countries',
|
||||
title: context.l10n.countryPageTitle,
|
||||
chipSetActionDelegate: CountryChipSetActionDelegate(),
|
||||
chipActionDelegate: ChipActionDelegate(),
|
||||
chipActionsBuilder: (filter) => [
|
||||
|
@ -39,7 +40,7 @@ class CountryListPage extends StatelessWidget {
|
|||
filterSections: _getCountryEntries(source),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.location,
|
||||
text: 'No countries',
|
||||
text: context.l10n.countryEmpty,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -6,7 +6,8 @@ import 'package:aves/model/source/collection_source.dart';
|
|||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/tag.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/filter_nav_page.dart';
|
||||
|
@ -29,7 +30,7 @@ class TagListPage extends StatelessWidget {
|
|||
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage<TagFilter>(
|
||||
source: source,
|
||||
title: 'Tags',
|
||||
title: context.l10n.tagPageTitle,
|
||||
chipSetActionDelegate: TagChipSetActionDelegate(),
|
||||
chipActionDelegate: ChipActionDelegate(),
|
||||
chipActionsBuilder: (filter) => [
|
||||
|
@ -39,7 +40,7 @@ class TagListPage extends StatelessWidget {
|
|||
filterSections: _getTagEntries(source),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.tag,
|
||||
text: 'No tags',
|
||||
text: context.l10n.tagEmpty,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -45,7 +45,7 @@ class ExpandableFilterRow extends StatelessWidget {
|
|||
IconButton(
|
||||
icon: Icon(isExpanded ? AIcons.collapse : AIcons.expand),
|
||||
onPressed: () => expandedNotifier.value = isExpanded ? null : title,
|
||||
tooltip: isExpanded ? 'Collapse' : 'Expand',
|
||||
tooltip: isExpanded ? MaterialLocalizations.of(context).expandedIconTapHint : MaterialLocalizations.of(context).collapsedIconTapHint,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -16,7 +16,7 @@ class CollectionSearchButton extends StatelessWidget {
|
|||
key: Key('search-button'),
|
||||
icon: Icon(AIcons.search),
|
||||
onPressed: () => _goToSearch(context),
|
||||
tooltip: 'Search',
|
||||
tooltip: MaterialLocalizations.of(context).searchFieldLabel,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,11 +15,13 @@ import 'package:aves/model/source/tag.dart';
|
|||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/search/expandable_filter_row.dart';
|
||||
import 'package:aves/widgets/search/search_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CollectionSearchDelegate {
|
||||
final CollectionSource source;
|
||||
|
@ -27,6 +29,16 @@ class CollectionSearchDelegate {
|
|||
final ValueNotifier<String> expandedSectionNotifier = ValueNotifier(null);
|
||||
|
||||
static const searchHistoryCount = 10;
|
||||
static final typeFilters = [
|
||||
FavouriteFilter(),
|
||||
MimeFilter(MimeTypes.anyImage),
|
||||
MimeFilter(MimeTypes.anyVideo),
|
||||
MimeFilter(MimeTypes.svg),
|
||||
TypeFilter(TypeFilter.animated),
|
||||
TypeFilter(TypeFilter.panorama),
|
||||
TypeFilter(TypeFilter.sphericalVideo),
|
||||
TypeFilter(TypeFilter.geotiff),
|
||||
];
|
||||
|
||||
CollectionSearchDelegate({@required this.source, this.parentCollection});
|
||||
|
||||
|
@ -58,7 +70,7 @@ class CollectionSearchDelegate {
|
|||
query = '';
|
||||
showSuggestions(context);
|
||||
},
|
||||
tooltip: 'Clear',
|
||||
tooltip: context.l10n.clearTooltip,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -71,84 +83,82 @@ class CollectionSearchDelegate {
|
|||
valueListenable: expandedSectionNotifier,
|
||||
builder: (context, expandedSection, child) {
|
||||
final queryFilter = _buildQueryFilter(false);
|
||||
final history = settings.searchHistory;
|
||||
return ListView(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
children: [
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
filters: [
|
||||
queryFilter,
|
||||
FavouriteFilter(),
|
||||
MimeFilter(MimeTypes.anyImage),
|
||||
MimeFilter(MimeTypes.anyVideo),
|
||||
MimeFilter(MimeTypes.svg),
|
||||
TypeFilter(TypeFilter.animated),
|
||||
TypeFilter(TypeFilter.panorama),
|
||||
TypeFilter(TypeFilter.sphericalVideo),
|
||||
TypeFilter(TypeFilter.geotiff),
|
||||
].where((f) => f != null && containQuery(f.label)).toList(),
|
||||
// usually perform hero animation only on tapped chips,
|
||||
// but we also need to animate the query chip when it is selected by submitting the search query
|
||||
heroTypeBuilder: (filter) => filter == queryFilter ? HeroType.always : HeroType.onTap,
|
||||
),
|
||||
if (upQuery.isEmpty && history.isNotEmpty)
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
title: 'Recent',
|
||||
filters: history,
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
// filter twice: full path, and then unique name
|
||||
final filters = source.rawAlbums.where(containQuery).map((s) => AlbumFilter(s, source.getUniqueAlbumName(s))).where((f) => containQuery(f.uniqueName)).toList()..sort();
|
||||
return _buildFilterRow(
|
||||
return Selector<Settings, Set<CollectionFilter>>(
|
||||
selector: (context, s) => s.hiddenFilters,
|
||||
builder: (context, hiddenFilters, child) {
|
||||
bool notHidden(CollectionFilter filter) => !hiddenFilters.contains(filter);
|
||||
final history = settings.searchHistory.where(notHidden).toList();
|
||||
return ListView(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
children: [
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
title: 'Albums',
|
||||
filters: filters,
|
||||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<CountriesChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)).toList();
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: 'Countries',
|
||||
filters: filters,
|
||||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<PlacesChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s));
|
||||
final noFilter = LocationFilter(LocationLevel.place, '');
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: 'Places',
|
||||
filters: [
|
||||
if (containQuery(LocationFilter.emptyLabel)) noFilter,
|
||||
...filters,
|
||||
],
|
||||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedTags.where(containQuery).map((s) => TagFilter(s));
|
||||
final noFilter = TagFilter('');
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: 'Tags',
|
||||
filters: [
|
||||
if (containQuery(TagFilter.emptyLabel)) noFilter,
|
||||
...filters,
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
queryFilter,
|
||||
...typeFilters.where(notHidden),
|
||||
].where((f) => f != null && containQuery(f.getLabel(context))).toList(),
|
||||
// usually perform hero animation only on tapped chips,
|
||||
// but we also need to animate the query chip when it is selected by submitting the search query
|
||||
heroTypeBuilder: (filter) => filter == queryFilter ? HeroType.always : HeroType.onTap,
|
||||
),
|
||||
if (upQuery.isEmpty && history.isNotEmpty)
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
title: context.l10n.searchSectionRecent,
|
||||
filters: history,
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
// filter twice: full path, and then unique name
|
||||
final filters = source.rawAlbums.where(containQuery).map((s) => AlbumFilter(s, source.getUniqueAlbumName(context, s))).where((f) => containQuery(f.uniqueName)).toList()..sort();
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: context.l10n.searchSectionAlbums,
|
||||
filters: filters,
|
||||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<CountriesChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)).toList();
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: context.l10n.searchSectionCountries,
|
||||
filters: filters,
|
||||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<PlacesChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s));
|
||||
final noFilter = LocationFilter(LocationLevel.place, '');
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: context.l10n.searchSectionPlaces,
|
||||
filters: [
|
||||
if (containQuery(noFilter.getLabel(context))) noFilter,
|
||||
...filters,
|
||||
],
|
||||
);
|
||||
}),
|
||||
StreamBuilder(
|
||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
final filters = source.sortedTags.where(containQuery).map((s) => TagFilter(s));
|
||||
final noFilter = TagFilter('');
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: context.l10n.searchSectionTags,
|
||||
filters: [
|
||||
if (containQuery(noFilter.getLabel(context))) noFilter,
|
||||
...filters,
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/utils/debouncer.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/search/search_delegate.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -11,8 +12,8 @@ class SearchPage extends StatefulWidget {
|
|||
final Animation<double> animation;
|
||||
|
||||
const SearchPage({
|
||||
this.delegate,
|
||||
this.animation,
|
||||
@required this.delegate,
|
||||
@required this.animation,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -118,7 +119,7 @@ class _SearchPageState extends State<SearchPage> {
|
|||
onSubmitted: (_) => widget.delegate.showResults(context),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Search collection',
|
||||
hintText: context.l10n.searchCollectionFieldHint,
|
||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'package:aves/services/android_file_service.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StorageAccessTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text('Storage Access'),
|
||||
title: Text(context.l10n.settingsStorageAccessTile),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
@ -44,7 +45,7 @@ class _StorageAccessPageState extends State<StorageAccessPage> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Storage Access'),
|
||||
title: Text(context.l10n.settingsStorageAccessTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
|
@ -56,7 +57,7 @@ class _StorageAccessPageState extends State<StorageAccessPage> {
|
|||
children: [
|
||||
Icon(AIcons.info),
|
||||
SizedBox(width: 16),
|
||||
Expanded(child: Text('Some directories require an explicit access grant to modify files in them. You can review here directories to which you previously gave access.')),
|
||||
Expanded(child: Text(context.l10n.settingsStorageAccessBanner)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -74,7 +75,7 @@ class _StorageAccessPageState extends State<StorageAccessPage> {
|
|||
_lastPaths = snapshot.data..sort();
|
||||
if (_lastPaths.isEmpty) {
|
||||
return EmptyContent(
|
||||
text: 'No access grants',
|
||||
text: context.l10n.settingsStorageAccessEmpty,
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
|
@ -90,7 +91,7 @@ class _StorageAccessPageState extends State<StorageAccessPage> {
|
|||
_load();
|
||||
setState(() {});
|
||||
},
|
||||
tooltip: 'Revoke',
|
||||
tooltip: context.l10n.settingsStorageAccessRevokeTooltip,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/settings/entry_background.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/widgets/common/fx/borders.dart';
|
||||
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -10,7 +11,7 @@ class HiddenFilterTile extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text('Hidden filters'),
|
||||
title: Text(context.l10n.settingsHiddenFiltersTile),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
@ -31,7 +32,7 @@ class HiddenFilterPage extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Hidden Filters'),
|
||||
title: Text(context.l10n.settingsHiddenFiltersTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
|
@ -43,7 +44,7 @@ class HiddenFilterPage extends StatelessWidget {
|
|||
children: [
|
||||
Icon(AIcons.info),
|
||||
SizedBox(width: 16),
|
||||
Expanded(child: Text('Photos and videos matching hidden filters will not appear in your collection.')),
|
||||
Expanded(child: Text(context.l10n.settingsHiddenFiltersBanner)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -58,7 +59,7 @@ class HiddenFilterPage extends StatelessWidget {
|
|||
if (hiddenFilters.isEmpty) {
|
||||
return EmptyContent(
|
||||
icon: AIcons.hide,
|
||||
text: 'No hidden filters',
|
||||
text: context.l10n.settingsHiddenFiltersEmpty,
|
||||
);
|
||||
}
|
||||
return Wrap(
|
||||
|
|
52
lib/widgets/settings/language.dart
Normal file
52
lib/widgets/settings/language.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
||||
|
||||
class LanguageTile extends StatelessWidget {
|
||||
final Locale _systemLocale = WidgetsBinding.instance.window.locale;
|
||||
|
||||
static const _systemLocaleOption = Locale('system');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final current = settings.locale;
|
||||
return ListTile(
|
||||
title: Text(context.l10n.settingsLanguage),
|
||||
subtitle: Text('${current == null ? '${context.l10n.settingsSystemDefault} • ${_getLocaleName(_systemLocale)}' : _getLocaleName(current)}'),
|
||||
onTap: () async {
|
||||
final value = await showDialog<Locale>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<Locale>(
|
||||
initialValue: settings.locale ?? _systemLocaleOption,
|
||||
options: _getLocaleOptions(context),
|
||||
optionSubtitleBuilder: (locale) => locale == _systemLocaleOption ? _getLocaleName(_systemLocale) : null,
|
||||
title: context.l10n.settingsLanguage,
|
||||
),
|
||||
);
|
||||
if (value != null) {
|
||||
settings.locale = value == _systemLocaleOption ? null : value;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _getLocaleName(Locale locale) => LocaleNamesLocalizationsDelegate.nativeLocaleNames[locale.toString()];
|
||||
|
||||
LinkedHashMap<Locale, String> _getLocaleOptions(BuildContext context) {
|
||||
final supportedLocales = List<Locale>.from(AppLocalizations.supportedLocales);
|
||||
supportedLocales.removeWhere((locale) => locale == _systemLocale);
|
||||
final displayLocales = supportedLocales.map((locale) => MapEntry(locale, _getLocaleName(locale))).toList()..sort((a, b) => compareAsciiUpperCase(a.value, b.value));
|
||||
|
||||
return LinkedHashMap.of({
|
||||
_systemLocaleOption: context.l10n.settingsSystemDefault,
|
||||
...LinkedHashMap.fromEntries(displayLocales),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
import 'package:aves/model/settings/coordinate_format.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/home_page.dart';
|
||||
import 'package:aves/model/settings/screen_on.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/settings/access_grants.dart';
|
||||
import 'package:aves/widgets/settings/entry_background.dart';
|
||||
import 'package:aves/widgets/settings/hidden_filters.dart';
|
||||
import 'package:aves/widgets/settings/language.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -30,7 +33,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
return MediaQueryDataProvider(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Settings'),
|
||||
title: Text(context.l10n.settingsPageTitle),
|
||||
),
|
||||
body: Theme(
|
||||
data: theme.copyWith(
|
||||
|
@ -73,19 +76,19 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
Widget _buildNavigationSection(BuildContext context) {
|
||||
return AvesExpansionTile(
|
||||
title: 'Navigation',
|
||||
title: context.l10n.settingsSectionNavigation,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text('Home'),
|
||||
subtitle: Text(settings.homePage.name),
|
||||
title: Text(context.l10n.settingsHome),
|
||||
subtitle: Text(settings.homePage.getName(context)),
|
||||
onTap: () async {
|
||||
final value = await showDialog<HomePageSetting>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<HomePageSetting>(
|
||||
initialValue: settings.homePage,
|
||||
options: Map.fromEntries(HomePageSetting.values.map((v) => MapEntry(v, v.name))),
|
||||
title: 'Home',
|
||||
options: Map.fromEntries(HomePageSetting.values.map((v) => MapEntry(v, v.getName(context)))),
|
||||
title: context.l10n.settingsHome,
|
||||
),
|
||||
);
|
||||
if (value != null) {
|
||||
|
@ -96,7 +99,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
SwitchListTile(
|
||||
value: settings.mustBackTwiceToExit,
|
||||
onChanged: (v) => settings.mustBackTwiceToExit = v,
|
||||
title: Text('Tap “back” twice to exit'),
|
||||
title: Text(context.l10n.settingsDoubleBackExit),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -104,19 +107,20 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
Widget _buildDisplaySection(BuildContext context) {
|
||||
return AvesExpansionTile(
|
||||
title: 'Display',
|
||||
title: context.l10n.settingsSectionDisplay,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: [
|
||||
LanguageTile(),
|
||||
ListTile(
|
||||
title: Text('Keep screen on'),
|
||||
subtitle: Text(settings.keepScreenOn.name),
|
||||
title: Text(context.l10n.settingsKeepScreenOnTile),
|
||||
subtitle: Text(settings.keepScreenOn.getName(context)),
|
||||
onTap: () async {
|
||||
final value = await showDialog<KeepScreenOn>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<KeepScreenOn>(
|
||||
initialValue: settings.keepScreenOn,
|
||||
options: Map.fromEntries(KeepScreenOn.values.map((v) => MapEntry(v, v.name))),
|
||||
title: 'Keep Screen On',
|
||||
options: Map.fromEntries(KeepScreenOn.values.map((v) => MapEntry(v, v.getName(context)))),
|
||||
title: context.l10n.settingsKeepScreenOnTitle,
|
||||
),
|
||||
);
|
||||
if (value != null) {
|
||||
|
@ -125,34 +129,30 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Raster image background'),
|
||||
title: Text(context.l10n.settingsRasterImageBackground),
|
||||
trailing: EntryBackgroundSelector(
|
||||
getter: () => settings.rasterBackground,
|
||||
setter: (value) => settings.rasterBackground = value,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Vector image background'),
|
||||
title: Text(context.l10n.settingsVectorImageBackground),
|
||||
trailing: EntryBackgroundSelector(
|
||||
getter: () => settings.vectorBackground,
|
||||
setter: (value) => settings.vectorBackground = value,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Coordinate format'),
|
||||
subtitle: Text(settings.coordinateFormat.name),
|
||||
title: Text(context.l10n.settingsCoordinateFormatTile),
|
||||
subtitle: Text(settings.coordinateFormat.getName(context)),
|
||||
onTap: () async {
|
||||
final value = await showDialog<CoordinateFormat>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<CoordinateFormat>(
|
||||
initialValue: settings.coordinateFormat,
|
||||
options: Map.fromEntries(CoordinateFormat.values.map((v) => MapEntry(v, v.name))),
|
||||
optionSubtitleBuilder: (dynamic value) {
|
||||
// dynamic declaration followed by cast, as workaround for generics limitation
|
||||
final formatter = (value as CoordinateFormat);
|
||||
return formatter.format(Constants.pointNemo);
|
||||
},
|
||||
title: 'Coordinate Format',
|
||||
options: Map.fromEntries(CoordinateFormat.values.map((v) => MapEntry(v, v.getName(context)))),
|
||||
optionSubtitleBuilder: (value) => value.format(Constants.pointNemo),
|
||||
title: context.l10n.settingsCoordinateFormatTitle,
|
||||
),
|
||||
);
|
||||
if (value != null) {
|
||||
|
@ -166,23 +166,23 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
Widget _buildThumbnailsSection(BuildContext context) {
|
||||
return AvesExpansionTile(
|
||||
title: 'Thumbnails',
|
||||
title: context.l10n.settingsSectionThumbnails,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
value: settings.showThumbnailLocation,
|
||||
onChanged: (v) => settings.showThumbnailLocation = v,
|
||||
title: Text('Show location icon'),
|
||||
title: Text(context.l10n.settingsThumbnailShowLocationIcon),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: settings.showThumbnailRaw,
|
||||
onChanged: (v) => settings.showThumbnailRaw = v,
|
||||
title: Text('Show raw icon'),
|
||||
title: Text(context.l10n.settingsThumbnailShowRawIcon),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: settings.showThumbnailVideoDuration,
|
||||
onChanged: (v) => settings.showThumbnailVideoDuration = v,
|
||||
title: Text('Show video duration'),
|
||||
title: Text(context.l10n.settingsThumbnailShowVideoDuration),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -190,24 +190,24 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
Widget _buildViewerSection(BuildContext context) {
|
||||
return AvesExpansionTile(
|
||||
title: 'Viewer',
|
||||
title: context.l10n.settingsSectionViewer,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
value: settings.showOverlayMinimap,
|
||||
onChanged: (v) => settings.showOverlayMinimap = v,
|
||||
title: Text('Show minimap'),
|
||||
title: Text(context.l10n.settingsViewerShowMinimap),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: settings.showOverlayInfo,
|
||||
onChanged: (v) => settings.showOverlayInfo = v,
|
||||
title: Text('Show information'),
|
||||
subtitle: Text('Show title, date, location, etc.'),
|
||||
title: Text(context.l10n.settingsViewerShowInformation),
|
||||
subtitle: Text(context.l10n.settingsViewerShowInformationSubtitle),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: settings.showOverlayShootingDetails,
|
||||
onChanged: settings.showOverlayInfo ? (v) => settings.showOverlayShootingDetails = v : null,
|
||||
title: Text('Show shooting details'),
|
||||
title: Text(context.l10n.settingsViewerShowShootingDetails),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -215,7 +215,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
Widget _buildSearchSection(BuildContext context) {
|
||||
return AvesExpansionTile(
|
||||
title: 'Search',
|
||||
title: context.l10n.settingsSectionSearch,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
|
@ -226,7 +226,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
settings.searchHistory = [];
|
||||
}
|
||||
},
|
||||
title: Text('Save search history'),
|
||||
title: Text(context.l10n.settingsSaveSearchHistory),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -234,13 +234,13 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
Widget _buildPrivacySection(BuildContext context) {
|
||||
return AvesExpansionTile(
|
||||
title: 'Privacy',
|
||||
title: context.l10n.settingsSectionPrivacy,
|
||||
expandedNotifier: _expandedNotifier,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
value: settings.isCrashlyticsEnabled,
|
||||
onChanged: (v) => settings.isCrashlyticsEnabled = v,
|
||||
title: Text('Allow anonymous analytics and crash reporting'),
|
||||
title: Text(context.l10n.settingsEnableAnalytics),
|
||||
),
|
||||
HiddenFilterTile(),
|
||||
StorageAccessTile(),
|
||||
|
|
|
@ -43,7 +43,7 @@ class FilterTable extends StatelessWidget {
|
|||
return Table(
|
||||
children: sortedEntries.take(5).map((kv) {
|
||||
final filter = filterBuilder(kv.key);
|
||||
final label = filter.label;
|
||||
final label = filter.getLabel(context);
|
||||
final count = kv.value;
|
||||
final percent = count / totalEntryCount;
|
||||
return TableRow(
|
||||
|
|
|
@ -12,7 +12,8 @@ import 'package:aves/utils/color_utils.dart';
|
|||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/utils/mime_utils.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/stats/filter_table.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
|
@ -62,24 +63,25 @@ class StatsPage extends StatelessWidget {
|
|||
if (entries.isEmpty) {
|
||||
child = EmptyContent(
|
||||
icon: AIcons.image,
|
||||
text: 'No images',
|
||||
text: context.l10n.collectionEmptyImages,
|
||||
);
|
||||
} else {
|
||||
final byMimeTypes = groupBy<AvesEntry, String>(entries, (entry) => entry.mimeType).map<String, int>((k, v) => MapEntry(k, v.length));
|
||||
final imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image/')));
|
||||
final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video/')));
|
||||
final imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image')));
|
||||
final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video')));
|
||||
final mimeDonuts = Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
_buildMimeDonut(context, (sum) => Intl.plural(sum, one: 'image', other: 'images'), imagesByMimeTypes),
|
||||
_buildMimeDonut(context, (sum) => Intl.plural(sum, one: 'video', other: 'videos'), videoByMimeTypes),
|
||||
_buildMimeDonut(context, (sum) => context.l10n.statsImage(sum), imagesByMimeTypes),
|
||||
_buildMimeDonut(context, (sum) => context.l10n.statsVideo(sum), videoByMimeTypes),
|
||||
],
|
||||
);
|
||||
|
||||
final catalogued = entries.where((entry) => entry.isCatalogued);
|
||||
final withGps = catalogued.where((entry) => entry.hasGps);
|
||||
final withGpsPercent = withGps.length / entries.length;
|
||||
final withGpsCount = withGps.length;
|
||||
final withGpsPercent = withGpsCount / entries.length;
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final lineHeight = 16 * textScaleFactor;
|
||||
final locationIndicator = Padding(
|
||||
|
@ -101,7 +103,7 @@ class StatsPage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text('${withGps.length} ${Intl.plural(withGps.length, one: 'item', other: 'items')} with location'),
|
||||
Text(context.l10n.statsWithGps(withGpsCount)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -109,16 +111,16 @@ class StatsPage extends StatelessWidget {
|
|||
children: [
|
||||
mimeDonuts,
|
||||
locationIndicator,
|
||||
..._buildTopFilters(context, 'Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)),
|
||||
..._buildTopFilters(context, 'Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)),
|
||||
..._buildTopFilters(context, 'Top Tags', entryCountPerTag, (s) => TagFilter(s)),
|
||||
..._buildTopFilters(context, context.l10n.statsTopCountries, entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)),
|
||||
..._buildTopFilters(context, context.l10n.statsTopPlaces, entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)),
|
||||
..._buildTopFilters(context, context.l10n.statsTopTags, entryCountPerTag, (s) => TagFilter(s)),
|
||||
],
|
||||
);
|
||||
}
|
||||
return MediaQueryDataProvider(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Stats'),
|
||||
title: Text(context.l10n.statsPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: child,
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:aves/services/metadata_service.dart';
|
|||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:aves/widgets/dialogs/rename_entry_dialog.dart';
|
||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
||||
|
@ -21,7 +22,6 @@ import 'package:aves/widgets/viewer/printer.dart';
|
|||
import 'package:aves/widgets/viewer/source_viewer_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -103,14 +103,14 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
if (!await checkStoragePermission(context, {entry})) return;
|
||||
|
||||
final success = await entry.flip();
|
||||
if (!success) showFeedback(context, 'Failed');
|
||||
if (!success) showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
|
||||
Future<void> _rotate(BuildContext context, AvesEntry entry, {@required bool clockwise}) async {
|
||||
if (!await checkStoragePermission(context, {entry})) return;
|
||||
|
||||
final success = await entry.rotate(clockwise: clockwise);
|
||||
if (!success) showFeedback(context, 'Failed');
|
||||
if (!success) showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteDialog(BuildContext context, AvesEntry entry) async {
|
||||
|
@ -119,15 +119,15 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
builder: (context) {
|
||||
return AvesDialog(
|
||||
context: context,
|
||||
content: Text('Are you sure?'),
|
||||
content: Text(context.l10n.deleteEntriesConfirmationDialogMessage(1)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text('Delete'.toUpperCase()),
|
||||
child: Text(context.l10n.deleteButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -138,7 +138,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
if (!await checkStoragePermission(context, {entry})) return;
|
||||
|
||||
if (!await entry.delete()) {
|
||||
showFeedback(context, 'Failed');
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
} else {
|
||||
if (hasCollection) {
|
||||
collection.source.removeEntries({entry.uri});
|
||||
|
@ -191,9 +191,9 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
final movedCount = movedOps.length;
|
||||
if (movedCount < selectionCount) {
|
||||
final count = selectionCount - movedCount;
|
||||
showFeedback(context, 'Failed to export ${Intl.plural(count, one: '$count page', other: '$count pages')}');
|
||||
showFeedback(context, context.l10n.collectionExportFailureFeedback(count));
|
||||
} else {
|
||||
showFeedback(context, 'Done!');
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -208,7 +208,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
|
||||
if (!await checkStoragePermission(context, {entry})) return;
|
||||
|
||||
showFeedback(context, await entry.rename(newName) ? 'Done!' : 'Failed');
|
||||
if (await entry.rename(newName)) {
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
} else {
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
|
||||
void _goToSourceViewer(BuildContext context, AvesEntry entry) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
|||
import 'package:aves/model/availability.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/settings/screen_on.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/services/window_service.dart';
|
||||
|
|
|
@ -10,8 +10,8 @@ import 'package:aves/model/source/collection_lens.dart';
|
|||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/metadata_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/utils/file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
@ -40,37 +40,40 @@ class BasicSection extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final infoUnknown = l10n.viewerInfoUnknown;
|
||||
final date = entry.bestDate;
|
||||
final dateText = date != null ? '${DateFormat.yMMMd().format(date)} • ${DateFormat.Hm().format(date)}' : Constants.infoUnknown;
|
||||
final locale = l10n.localeName;
|
||||
final dateText = date != null ? '${DateFormat.yMMMd(locale).format(date)} • ${DateFormat.Hm(locale).format(date)}' : infoUnknown;
|
||||
|
||||
// TODO TLAD line break on all characters for the following fields when this is fixed: https://github.com/flutter/flutter/issues/61081
|
||||
// inserting ZWSP (\u200B) between characters does help, but it messes with width and height computation (another Flutter issue)
|
||||
final title = entry.bestTitle ?? Constants.infoUnknown;
|
||||
final uri = entry.uri ?? Constants.infoUnknown;
|
||||
final title = entry.bestTitle ?? infoUnknown;
|
||||
final uri = entry.uri ?? infoUnknown;
|
||||
final path = entry.path;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InfoRowGroup({
|
||||
'Title': title,
|
||||
'Date': dateText,
|
||||
if (entry.isVideo) ..._buildVideoRows(),
|
||||
if (!entry.isSvg && entry.isSized) 'Resolution': rasterResolutionText,
|
||||
'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : Constants.infoUnknown,
|
||||
'URI': uri,
|
||||
if (path != null) 'Path': path,
|
||||
l10n.viewerInfoLabelTitle: title,
|
||||
l10n.viewerInfoLabelDate: dateText,
|
||||
if (entry.isVideo) ..._buildVideoRows(context),
|
||||
if (!entry.isSvg && entry.isSized) l10n.viewerInfoLabelResolution: rasterResolutionText,
|
||||
l10n.viewerInfoLabelSize: entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : infoUnknown,
|
||||
l10n.viewerInfoLabelUri: uri,
|
||||
if (path != null) l10n.viewerInfoLabelPath: path,
|
||||
}),
|
||||
OwnerProp(
|
||||
entry: entry,
|
||||
visibleNotifier: visibleNotifier,
|
||||
),
|
||||
_buildChips(),
|
||||
_buildChips(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChips() {
|
||||
Widget _buildChips(BuildContext context) {
|
||||
final tags = entry.xmpSubjects..sort(compareAsciiUpperCase);
|
||||
final album = entry.directory;
|
||||
final filters = {
|
||||
|
@ -80,7 +83,7 @@ class BasicSection extends StatelessWidget {
|
|||
if (entry.isImage && entry.is360) TypeFilter(TypeFilter.panorama),
|
||||
if (entry.isVideo && entry.is360) TypeFilter(TypeFilter.sphericalVideo),
|
||||
if (entry.isVideo && !entry.is360) MimeFilter(MimeTypes.anyVideo),
|
||||
if (album != null) AlbumFilter(album, collection?.source?.getUniqueAlbumName(album)),
|
||||
if (album != null) AlbumFilter(album, collection?.source?.getUniqueAlbumName(context, album)),
|
||||
...tags.map((tag) => TagFilter(tag)),
|
||||
};
|
||||
return AnimatedBuilder(
|
||||
|
@ -108,9 +111,9 @@ class BasicSection extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Map<String, String> _buildVideoRows() {
|
||||
Map<String, String> _buildVideoRows(BuildContext context) {
|
||||
return {
|
||||
'Duration': entry.durationText,
|
||||
context.l10n.viewerInfoLabelDuration: entry.durationText,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +183,7 @@ class _OwnerPropState extends State<OwnerProp> {
|
|||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Owned by',
|
||||
text: context.l10n.viewerInfoLabelOwner,
|
||||
style: InfoRowGroup.keyStyle,
|
||||
),
|
||||
WidgetSpan(
|
||||
|
|
|
@ -98,7 +98,7 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
|
||||
if (linkHandlers?.containsKey(key) == true) {
|
||||
final handler = linkHandlers[key];
|
||||
value = handler.linkText;
|
||||
value = handler.linkText(context);
|
||||
// open link on tap
|
||||
recognizer = TapGestureRecognizer()..onTap = () => handler.onTap(context);
|
||||
style = InfoRowGroup.linkStyle;
|
||||
|
@ -149,7 +149,7 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
}
|
||||
|
||||
class InfoLinkHandler {
|
||||
final String linkText;
|
||||
final String Function(BuildContext context) linkText;
|
||||
final void Function(BuildContext context) onTap;
|
||||
|
||||
const InfoLinkHandler({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/app_bar_title.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/viewer/info/info_search.dart';
|
||||
import 'package:aves/widgets/viewer/info/metadata/metadata_section.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -23,17 +24,17 @@ class InfoAppBar extends StatelessWidget {
|
|||
key: Key('back-button'),
|
||||
icon: Icon(AIcons.goUp),
|
||||
onPressed: onBackPressed,
|
||||
tooltip: 'Back to viewer',
|
||||
tooltip: context.l10n.viewerInfoBackToViewerTooltip,
|
||||
),
|
||||
title: TappableAppBarTitle(
|
||||
title: InteractiveAppBarTitle(
|
||||
onTap: () => _goToSearch(context),
|
||||
child: Text('Info'),
|
||||
child: Text(context.l10n.viewerInfoPageTitle),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(AIcons.search),
|
||||
onPressed: () => _goToSearch(context),
|
||||
tooltip: 'Search',
|
||||
tooltip: MaterialLocalizations.of(context).searchFieldLabel,
|
||||
),
|
||||
],
|
||||
titleSpacing: 0,
|
||||
|
@ -45,6 +46,7 @@ class InfoAppBar extends StatelessWidget {
|
|||
showSearch(
|
||||
context: context,
|
||||
delegate: InfoSearchDelegate(
|
||||
searchFieldLabel: context.l10n.viewerInfoSearchFieldLabel,
|
||||
entry: entry,
|
||||
metadataNotifier: metadataNotifier,
|
||||
),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
||||
import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart';
|
||||
import 'package:aves/widgets/viewer/info/metadata/metadata_section.dart';
|
||||
|
@ -15,19 +16,12 @@ class InfoSearchDelegate extends SearchDelegate {
|
|||
|
||||
Map<String, MetadataDirectory> get metadata => metadataNotifier.value;
|
||||
|
||||
static const suggestions = {
|
||||
'Date & time': 'date or time or when -timer -uptime -exposure -timeline',
|
||||
'Description': 'abstract or description or comment or textual',
|
||||
'Dimensions': 'width or height or dimension or framesize or imagelength',
|
||||
'Resolution': 'resolution',
|
||||
'Rights': 'rights or copyright or artist or creator or by-line or credit -tool',
|
||||
};
|
||||
|
||||
InfoSearchDelegate({
|
||||
@required String searchFieldLabel,
|
||||
@required this.entry,
|
||||
@required this.metadataNotifier,
|
||||
}) : super(
|
||||
searchFieldLabel: 'Search metadata',
|
||||
searchFieldLabel: searchFieldLabel,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -57,23 +51,33 @@ class InfoSearchDelegate extends SearchDelegate {
|
|||
query = '';
|
||||
showSuggestions(context);
|
||||
},
|
||||
tooltip: 'Clear',
|
||||
tooltip: context.l10n.clearTooltip,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) => ListView(
|
||||
children: suggestions.entries
|
||||
.map((kv) => ListTile(
|
||||
title: Text(kv.key),
|
||||
onTap: () {
|
||||
query = kv.value;
|
||||
showResults(context);
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final suggestions = {
|
||||
l10n.viewerInfoSearchSuggestionDate: 'date or time or when -timer -uptime -exposure -timeline',
|
||||
l10n.viewerInfoSearchSuggestionDescription: 'abstract or description or comment or textual',
|
||||
l10n.viewerInfoSearchSuggestionDimensions: 'width or height or dimension or framesize or imagelength',
|
||||
l10n.viewerInfoSearchSuggestionResolution: 'resolution',
|
||||
l10n.viewerInfoSearchSuggestionRights: 'rights or copyright or artist or creator or by-line or credit -tool',
|
||||
};
|
||||
return ListView(
|
||||
children: suggestions.entries
|
||||
.map((kv) => ListTile(
|
||||
title: Text(kv.key),
|
||||
onTap: () {
|
||||
query = kv.value;
|
||||
showResults(context);
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
|
@ -107,22 +111,24 @@ class InfoSearchDelegate extends SearchDelegate {
|
|||
showPrefixChildren: false,
|
||||
))
|
||||
.toList();
|
||||
return tiles.isEmpty
|
||||
? EmptyContent(
|
||||
icon: AIcons.info,
|
||||
text: 'No matching keys',
|
||||
)
|
||||
: NotificationListener<OpenTempEntryNotification>(
|
||||
onNotification: (notification) {
|
||||
_openTempEntry(context, notification.entry);
|
||||
return true;
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.all(8),
|
||||
itemBuilder: (context, index) => tiles[index],
|
||||
itemCount: tiles.length,
|
||||
return SafeArea(
|
||||
child: tiles.isEmpty
|
||||
? EmptyContent(
|
||||
icon: AIcons.info,
|
||||
text: context.l10n.viewerInfoSearchEmpty,
|
||||
)
|
||||
: NotificationListener<OpenTempEntryNotification>(
|
||||
onNotification: (notification) {
|
||||
_openTempEntry(context, notification.entry);
|
||||
return true;
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.all(8),
|
||||
itemBuilder: (context, index) => tiles[index],
|
||||
itemCount: tiles.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
void _openTempEntry(BuildContext context, AvesEntry tempEntry) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/model/settings/settings.dart';
|
|||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:aves/widgets/viewer/info/maps/common.dart';
|
||||
|
@ -195,9 +196,10 @@ class _AddressInfoGroupState extends State<_AddressInfoGroup> {
|
|||
future: _addressLineLoader,
|
||||
builder: (context, snapshot) {
|
||||
final address = !snapshot.hasError && snapshot.connectionState == ConnectionState.done ? snapshot.data : null;
|
||||
final l10n = context.l10n;
|
||||
return InfoRowGroup({
|
||||
'Coordinates': settings.coordinateFormat.format(entry.latLng),
|
||||
if (address?.isNotEmpty == true) 'Address': address,
|
||||
l10n.viewerInfoLabelCoordinates: settings.coordinateFormat.format(entry.latLng),
|
||||
if (address?.isNotEmpty == true) l10n.viewerInfoLabelAddress: address,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'package:aves/model/availability.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/map_style.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||
import 'package:aves/widgets/common/fx/borders.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
|
@ -68,7 +70,7 @@ class MapButtonPanel extends StatelessWidget {
|
|||
onPressed: () => AndroidAppService.openMap(geoUri).then((success) {
|
||||
if (!success) showNoMatchingAppDialog(context);
|
||||
}),
|
||||
tooltip: 'Show on map…',
|
||||
tooltip: context.l10n.entryActionOpenMap,
|
||||
),
|
||||
SizedBox(height: padding),
|
||||
MapOverlayButton(
|
||||
|
@ -83,8 +85,8 @@ class MapButtonPanel extends StatelessWidget {
|
|||
builder: (context) {
|
||||
return AvesSelectionDialog<EntryMapStyle>(
|
||||
initialValue: initialStyle,
|
||||
options: Map.fromEntries(availableStyles.map((v) => MapEntry(v, v.name))),
|
||||
title: 'Map Style',
|
||||
options: Map.fromEntries(availableStyles.map((v) => MapEntry(v, v.getName(context)))),
|
||||
title: context.l10n.viewerInfoMapStyleTitle,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -95,19 +97,19 @@ class MapButtonPanel extends StatelessWidget {
|
|||
MapStyleChangedNotification().dispatch(context);
|
||||
}
|
||||
},
|
||||
tooltip: 'Style map…',
|
||||
tooltip: context.l10n.viewerInfoMapStyleTooltip,
|
||||
),
|
||||
Spacer(),
|
||||
MapOverlayButton(
|
||||
icon: AIcons.zoomIn,
|
||||
onPressed: () => zoomBy(1),
|
||||
tooltip: 'Zoom in',
|
||||
tooltip: context.l10n.viewerInfoMapZoomInTooltip,
|
||||
),
|
||||
SizedBox(height: padding),
|
||||
MapOverlayButton(
|
||||
icon: AIcons.zoomOut,
|
||||
onPressed: () => zoomBy(-1),
|
||||
tooltip: 'Zoom out',
|
||||
tooltip: context.l10n.viewerInfoMapZoomOutTooltip,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aves/model/settings/map_style.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/viewer/info/maps/common.dart';
|
||||
import 'package:aves/widgets/viewer/info/maps/marker.dart';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue