diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8de3150aa..896ce5ddd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.7.10] - 2023-01-18
+
+### Added
+
+- Video: optional gestures to adjust brightness/volume
+- TV: improved support for Search, About, Privacy Policy
+
+### Changed
+
+- Viewer: do not keep max brightness when viewing info
+
+### Fixed
+
+- crash when media button events are triggered with no active media session
+
## [v1.7.9] - 2023-01-15
### Added
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 64f920264..5d8bc1ce9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -213,6 +213,15 @@ This change eventually prevents building the app with Flutter v3.3.3.
+
+
+
+
+
+
>) {
+ val children = mutableListOf()
+ result.sendResult(children)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
index 715b155b5..d42e5ea50 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
@@ -12,7 +12,7 @@ import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.metadata.*
-import deckers.thibault.aves.metadata.XMP.doesPropExist
+import deckers.thibault.aves.metadata.XMP.doesPropPathExist
import deckers.thibault.aves.metadata.XMP.getSafeStructField
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap
@@ -104,7 +104,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
try {
container = xmpDirs.firstNotNullOfOrNull {
val xmpMeta = it.xmpMeta
- if (xmpMeta.doesPropExist(XMP.GDEVICE_DIRECTORY_PROP_NAME)) {
+ if (xmpMeta.doesPropPathExist(listOf(XMP.GDEVICE_CONTAINER_PROP_NAME, XMP.GDEVICE_CONTAINER_DIRECTORY_PROP_NAME))) {
GoogleDeviceContainer().apply { findItems(xmpMeta) }
} else {
null
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GoogleDeviceContainer.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GoogleDeviceContainer.kt
index 590b1f65d..a481c94e4 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GoogleDeviceContainer.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GoogleDeviceContainer.kt
@@ -3,7 +3,7 @@ package deckers.thibault.aves.metadata
import android.content.Context
import android.net.Uri
import com.adobe.internal.xmp.XMPMeta
-import deckers.thibault.aves.metadata.XMP.countPropArrayItems
+import deckers.thibault.aves.metadata.XMP.countPropPathArrayItems
import deckers.thibault.aves.metadata.XMP.getSafeStructField
import deckers.thibault.aves.utils.indexOfBytes
import java.io.DataInputStream
@@ -15,11 +15,12 @@ class GoogleDeviceContainer {
private val offsets: MutableList = ArrayList()
fun findItems(xmpMeta: XMPMeta) {
- val count = xmpMeta.countPropArrayItems(XMP.GDEVICE_DIRECTORY_PROP_NAME)
+ val containerDirectoryPath = listOf(XMP.GDEVICE_CONTAINER_PROP_NAME, XMP.GDEVICE_CONTAINER_DIRECTORY_PROP_NAME)
+ val count = xmpMeta.countPropPathArrayItems(containerDirectoryPath)
for (i in 1 until count + 1) {
- val mimeType = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME))?.value
- val length = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME))?.value?.toLongOrNull()
- val dataUri = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME))?.value
+ val mimeType = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME))?.value
+ val length = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME))?.value?.toLongOrNull()
+ val dataUri = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME))?.value
if (mimeType != null && length != null && dataUri != null) {
items.add(
GoogleDeviceContainerItem(
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt
index 737c862da..74a9a9a3d 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt
@@ -49,6 +49,7 @@ object XMP {
private const val GCONTAINER_ITEM_NS_URI = "http://ns.google.com/photos/1.0/container/item/"
private const val GDEPTH_NS_URI = "http://ns.google.com/photos/1.0/depthmap/"
private const val GDEVICE_NS_URI = "http://ns.google.com/photos/dd/1.0/device/"
+ private const val GDEVICE_CONTAINER_NS_URI = "http://ns.google.com/photos/dd/1.0/container/"
private const val GDEVICE_ITEM_NS_URI = "http://ns.google.com/photos/dd/1.0/item/"
private const val GIMAGE_NS_URI = "http://ns.google.com/photos/1.0/image/"
private const val GPANO_NS_URI = "http://ns.google.com/photos/1.0/panorama/"
@@ -70,6 +71,7 @@ object XMP {
// cf https://developers.google.com/vr/reference/cardboard-camera-vr-photo-format
private val knownDataProps = listOf(
XMPPropName(GAUDIO_NS_URI, "Data"),
+ XMPPropName(GCAMERA_NS_URI, "RelitInputImageData"),
XMPPropName(GIMAGE_NS_URI, "Data"),
XMPPropName(GDEPTH_NS_URI, "Data"),
XMPPropName(GDEPTH_NS_URI, "Confidence"),
@@ -79,7 +81,8 @@ object XMP {
// google portrait
- val GDEVICE_DIRECTORY_PROP_NAME = XMPPropName(GDEVICE_NS_URI, "Container/Container:Directory")
+ val GDEVICE_CONTAINER_PROP_NAME = XMPPropName(GDEVICE_NS_URI, "Container")
+ val GDEVICE_CONTAINER_DIRECTORY_PROP_NAME = XMPPropName(GDEVICE_CONTAINER_NS_URI, "Directory")
val GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "DataURI")
val GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Length")
val GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Mime")
@@ -254,10 +257,18 @@ object XMP {
return doesPropertyExist(prop.nsUri, prop.toString())
}
+ fun XMPMeta.doesPropPathExist(props: List): Boolean {
+ return doesPropertyExist(props.first().nsUri, props.joinToString("/"))
+ }
+
fun XMPMeta.countPropArrayItems(prop: XMPPropName): Int {
return countArrayItems(prop.nsUri, prop.toString())
}
+ fun XMPMeta.countPropPathArrayItems(props: List): Int {
+ return countArrayItems(props.first().nsUri, props.joinToString("/"))
+ }
+
fun XMPMeta.getPropArrayItemValues(prop: XMPPropName): List {
val schema = prop.nsUri
val propName = prop.toString()
diff --git a/android/app/src/main/res/values-iw/strings.xml b/android/app/src/main/res/values-iw/strings.xml
new file mode 100644
index 000000000..fa2bc3053
--- /dev/null
+++ b/android/app/src/main/res/values-iw/strings.xml
@@ -0,0 +1,12 @@
+
+
+ אייבז
+ מסגרת תמונה
+ טפט
+ חיפוש
+ סרטים
+ סריקת מדיה
+ סרוק תמונות וסרטים
+ סורק מדיה
+ הפסק
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values-pl/strings.xml b/android/app/src/main/res/values-pl/strings.xml
index 1e53b15c3..28f72abad 100644
--- a/android/app/src/main/res/values-pl/strings.xml
+++ b/android/app/src/main/res/values-pl/strings.xml
@@ -2,10 +2,10 @@
Ramka Zdjęcia
Szukaj
- Filmy
- Skan mediów
- Skan obrazów & filmów
- Skanowanie mediów
+ Wideo
+ Przeskanuj multimedia
+ Przeskanuj obrazy oraz wideo
+ Skanowanie multimediów
Zatrzymaj
Aves
Tapeta
diff --git a/fastlane/metadata/android/en-US/changelogs/90.txt b/fastlane/metadata/android/en-US/changelogs/90.txt
new file mode 100644
index 000000000..7683711d3
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/90.txt
@@ -0,0 +1,5 @@
+In v1.7.10:
+- Android TV support (cont'd)
+- interact with videos via media session controls
+- enjoy the app in Czech & Polish
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/9001.txt b/fastlane/metadata/android/en-US/changelogs/9001.txt
new file mode 100644
index 000000000..7683711d3
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/9001.txt
@@ -0,0 +1,5 @@
+In v1.7.10:
+- Android TV support (cont'd)
+- interact with videos via media session controls
+- enjoy the app in Czech & Polish
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/he/full_description.txt b/fastlane/metadata/android/he/full_description.txt
new file mode 100644
index 000000000..6c92748f8
--- /dev/null
+++ b/fastlane/metadata/android/he/full_description.txt
@@ -0,0 +1,5 @@
+Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more! It scans your media collection to identify motion photos, panoramas (aka photo spheres), 360° videos, as well as GeoTIFF files.
+
+Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc.
+
+Aves integrates with Android (from KitKat to Android 13, including Android TV) with features such as widgets, app shortcuts, screen saver and global search handling. It also works as a media viewer and picker.
\ No newline at end of file
diff --git a/fastlane/metadata/android/he/short_description.txt b/fastlane/metadata/android/he/short_description.txt
new file mode 100644
index 000000000..8c9445bd5
--- /dev/null
+++ b/fastlane/metadata/android/he/short_description.txt
@@ -0,0 +1 @@
+Gallery and metadata explorer
\ No newline at end of file
diff --git a/fastlane/metadata/android/pl/full_description.txt b/fastlane/metadata/android/pl/full_description.txt
index 2c26d7bb2..c10a1a6c6 100644
--- a/fastlane/metadata/android/pl/full_description.txt
+++ b/fastlane/metadata/android/pl/full_description.txt
@@ -1,4 +1,4 @@
-Aves obsługuje wszelkiego rodzaju obrazy i filmy, w tym typowe pliki JPEG i MP4 ale także bardziej egzotyczne formaty takie jak wielostronnicowe pliki TIFF, SVG, stare pliki AVI i wiele więcej! Skanuje twoją kolekcję multimediów aby zidentyfikować ruchome zdjęcia, panoramy (inaczej zdjęcia sferyczne), filmy 360°, a także pliki GeoTIFF.
+Aves obsługuje wszelkiego rodzaju obrazy i filmy, w tym typowe pliki JPEG i MP4, ale także bardziej egzotyczne formaty, takie jak wielostronicowe pliki TIFF, SVG, stare pliki AVI i wiele innych! Skanuje twoją kolekcję multimediów, aby zidentyfikować ruchome zdjęcia, zdjęcia panoramiczne (inaczej zdjęcia sferyczne), wideo 360°, a także pliki GeoTIFF.
Nawigacja i wyszukiwanie jest ważną częścią Aves. Celem jest aby użytkownicy mogli łatwo przechodzić od albumów do zdjęć, tagów, map itd.
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 52c9ab346..1141b9bfb 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -781,6 +781,7 @@
"settingsVideoButtonsTile": "Buttons",
"settingsVideoGestureDoubleTapTogglePlay": "Double tap to play/pause",
"settingsVideoGestureSideDoubleTapSeek": "Double tap on screen edges to seek backward/forward",
+ "settingsVideoGestureVerticalDragBrightnessVolume": "Swipe up or down to adjust brightness/volume",
"settingsPrivacySectionTitle": "Privacy",
"settingsAllowInstalledAppAccess": "Allow access to app inventory",
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index 3cf489059..e4609e5fd 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -1206,5 +1206,9 @@
"filterLocatedLabel": "Localizado",
"@filterLocatedLabel": {},
"filterTaggedLabel": "Etiquetado",
- "@filterTaggedLabel": {}
+ "@filterTaggedLabel": {},
+ "tooManyItemsErrorDialogMessage": "Vuelva a intentarlo con menos elementos.",
+ "@tooManyItemsErrorDialogMessage": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "Deslice hacia arriba o hacia abajo para ajustar el brillo o el volumen",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {}
}
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 90c5e9903..c62ac6bb8 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -1208,5 +1208,7 @@
"filterLocatedLabel": "Localisé",
"@filterLocatedLabel": {},
"tooManyItemsErrorDialogMessage": "Réessayez avec moins d’éléments.",
- "@tooManyItemsErrorDialogMessage": {}
+ "@tooManyItemsErrorDialogMessage": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "Balayer verticalement pour ajuster la luminosité et le volume",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {}
}
diff --git a/lib/l10n/app_he.arb b/lib/l10n/app_he.arb
new file mode 100644
index 000000000..d73ba56f5
--- /dev/null
+++ b/lib/l10n/app_he.arb
@@ -0,0 +1,10 @@
+{
+ "appName": "אייבז",
+ "@appName": {},
+ "welcomeMessage": "ברוך הבא לאייבז",
+ "@welcomeMessage": {},
+ "welcomeOptional": "אופציונלי",
+ "@welcomeOptional": {},
+ "welcomeTermsToggle": "אני מסכימ/ה לתנאים",
+ "@welcomeTermsToggle": {}
+}
diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb
index 9b1209d1e..1d9639b19 100644
--- a/lib/l10n/app_id.arb
+++ b/lib/l10n/app_id.arb
@@ -1208,5 +1208,7 @@
"filterTaggedLabel": "Dilabel",
"@filterTaggedLabel": {},
"tooManyItemsErrorDialogMessage": "Coba lagi dengan item yang lebih sedikit.",
- "@tooManyItemsErrorDialogMessage": {}
+ "@tooManyItemsErrorDialogMessage": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "Usap ke atas atau bawah untuk mengatur kecerahan/volume",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {}
}
diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb
index 5a62c46eb..4a3c0ee2a 100644
--- a/lib/l10n/app_ko.arb
+++ b/lib/l10n/app_ko.arb
@@ -1208,5 +1208,7 @@
"filterLocatedLabel": "위치 있음",
"@filterLocatedLabel": {},
"tooManyItemsErrorDialogMessage": "항목 수를 줄이고 다시 시도하세요.",
- "@tooManyItemsErrorDialogMessage": {}
+ "@tooManyItemsErrorDialogMessage": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "위아래로 스와이프해서 밝기/음량을 조절하기",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {}
}
diff --git a/lib/l10n/app_nb.arb b/lib/l10n/app_nb.arb
index aaedbab80..0c98e35ba 100644
--- a/lib/l10n/app_nb.arb
+++ b/lib/l10n/app_nb.arb
@@ -1265,7 +1265,7 @@
"count": {}
}
},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Slett disse albumene og deres element?} other{Slett disse albumene og deres {count} elementer?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Slett disse albumene og elementet i demt?} other{Slett disse albumene og de {count} elementene i dem?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -1338,5 +1338,33 @@
"albumTierSpecial": "Ofte åpnet",
"@albumTierSpecial": {},
"editEntryLocationDialogTitle": "Plassering",
- "@editEntryLocationDialogTitle": {}
+ "@editEntryLocationDialogTitle": {},
+ "filterLocatedLabel": "Posisjonert",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Etikettmerket",
+ "@filterTaggedLabel": {},
+ "settingsDisplayUseTvInterface": "Android TV-grensesnitt",
+ "@settingsDisplayUseTvInterface": {},
+ "settingsAccessibilityShowPinchGestureAlternatives": "Vis multi-trykkhåndvendingsalternativer",
+ "@settingsAccessibilityShowPinchGestureAlternatives": {},
+ "columnCount": "{count, plural, =1{1 kolonne} other{{count} kolonner}}",
+ "@columnCount": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "keepScreenOnVideoPlayback": "Under videoavspilling",
+ "@keepScreenOnVideoPlayback": {},
+ "entryActionShareImageOnly": "Del kun bilde",
+ "@entryActionShareImageOnly": {},
+ "entryActionShareVideoOnly": "Del kun video",
+ "@entryActionShareVideoOnly": {},
+ "entryInfoActionRemoveLocation": "Fjern posisjon",
+ "@entryInfoActionRemoveLocation": {},
+ "settingsViewerShowDescription": "Vis beskrivelse",
+ "@settingsViewerShowDescription": {},
+ "settingsModificationWarningDialogMessage": "Andre innstillinger vil bli endret.",
+ "@settingsModificationWarningDialogMessage": {},
+ "tooManyItemsErrorDialogMessage": "Prøv igjen med færre elementer.",
+ "@tooManyItemsErrorDialogMessage": {}
}
diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb
index d8bc6fbe4..2ef13f337 100644
--- a/lib/l10n/app_pl.arb
+++ b/lib/l10n/app_pl.arb
@@ -13,7 +13,7 @@
"@resetTooltip": {},
"pickTooltip": "Wybierz",
"@pickTooltip": {},
- "doubleBackExitMessage": "Tapnij ponownie „wstecz” aby wyjść.",
+ "doubleBackExitMessage": "Dotknij ponownie „wstecz”, aby wyjść.",
"@doubleBackExitMessage": {},
"saveTooltip": "Zapisz",
"@saveTooltip": {},
@@ -29,7 +29,7 @@
"@appName": {},
"welcomeMessage": "Witaj w Aves",
"@welcomeMessage": {},
- "welcomeOptional": "Opcjonalny",
+ "welcomeOptional": "Opcjonalnie",
"@welcomeOptional": {},
"welcomeTermsToggle": "Akceptuję warunki i zasady",
"@welcomeTermsToggle": {},
@@ -99,9 +99,9 @@
"@entryInfoActionEditTitleDescription": {},
"entryInfoActionEditRating": "Edytuj ocenę",
"@entryInfoActionEditRating": {},
- "entryInfoActionEditTags": "Edytuj tagi",
+ "entryInfoActionEditTags": "Edytuj znaczniki",
"@entryInfoActionEditTags": {},
- "entryInfoActionRemoveMetadata": "Usuń metadatę",
+ "entryInfoActionRemoveMetadata": "Usuń metadane",
"@entryInfoActionRemoveMetadata": {},
"filterBinLabel": "Kosz",
"@filterBinLabel": {},
@@ -111,17 +111,17 @@
"@filterNoDateLabel": {},
"filterNoRatingLabel": "Nieoceniony",
"@filterNoRatingLabel": {},
- "filterNoTagLabel": "Nieoznakowany",
+ "filterNoTagLabel": "Nieoznaczone",
"@filterNoTagLabel": {},
"filterNoTitleLabel": "Bez tytułu",
"@filterNoTitleLabel": {},
"filterOnThisDayLabel": "Tego dnia",
"@filterOnThisDayLabel": {},
- "filterRecentlyAddedLabel": "Ostatnio dodany",
+ "filterRecentlyAddedLabel": "Ostatnio dodane",
"@filterRecentlyAddedLabel": {},
"filterTypeMotionPhotoLabel": "Ruchome Zdjęcie",
"@filterTypeMotionPhotoLabel": {},
- "filterTypePanoramaLabel": "Zdjęcie sferyczne",
+ "filterTypePanoramaLabel": "Zdjęcie panoramiczne",
"@filterTypePanoramaLabel": {},
"entryActionFlip": "Obróć w poziomie",
"@entryActionFlip": {},
@@ -171,7 +171,7 @@
"@entryActionSetAs": {},
"entryActionAddFavourite": "Dodaj do ulubionych",
"@entryActionAddFavourite": {},
- "filterNoLocationLabel": "Nieumiejscowiony",
+ "filterNoLocationLabel": "Nieumiejscowione",
"@filterNoLocationLabel": {},
"filterRatingRejectedLabel": "Odrzucony",
"@filterRatingRejectedLabel": {},
@@ -193,9 +193,9 @@
"@nameConflictStrategySkip": {},
"videoLoopModeAlways": "Zawsze",
"@videoLoopModeAlways": {},
- "filterLocatedLabel": "Usytuowany",
+ "filterLocatedLabel": "Umiejscowione",
"@filterLocatedLabel": {},
- "filterTaggedLabel": "Oznaczony",
+ "filterTaggedLabel": "Oznaczone",
"@filterTaggedLabel": {},
"nameConflictStrategyReplace": "Zastąp",
"@nameConflictStrategyReplace": {},
@@ -211,9 +211,9 @@
"@filterAspectRatioPortraitLabel": {},
"filterNoAddressLabel": "Brak adresu",
"@filterNoAddressLabel": {},
- "videoControlsPlaySeek": "Odtwórz i szukaj do przodu/do tyłu",
+ "videoControlsPlaySeek": "Odtwórz oraz przeszukuj",
"@videoControlsPlaySeek": {},
- "videoControlsPlayOutside": "Otwórz w innym odtwarzaczu",
+ "videoControlsPlayOutside": "Odtwórz innym odtwarzaczem",
"@videoControlsPlayOutside": {},
"mapStyleGoogleNormal": "Mapy Google",
"@mapStyleGoogleNormal": {},
@@ -229,7 +229,7 @@
"@nameConflictStrategyRename": {},
"mapStyleOsmHot": "Humanitarny OSM",
"@mapStyleOsmHot": {},
- "keepScreenOnVideoPlayback": "Podczas odtwarzania wideo",
+ "keepScreenOnVideoPlayback": "Przy odtwarzaniu wideo",
"@keepScreenOnVideoPlayback": {},
"displayRefreshRatePreferLowest": "Najniższa",
"@displayRefreshRatePreferLowest": {},
@@ -327,11 +327,11 @@
"@filterTypeGeotiffLabel": {},
"filterMimeImageLabel": "Obraz",
"@filterMimeImageLabel": {},
- "unitSystemImperial": "Imperialny",
+ "unitSystemImperial": "Imperialne",
"@unitSystemImperial": {},
"videoLoopModeNever": "Nigdy",
"@videoLoopModeNever": {},
- "videoControlsNone": "Nic",
+ "videoControlsNone": "Brak",
"@videoControlsNone": {},
"accessibilityAnimationsRemove": "Zapobiegaj efektom ekranu",
"@accessibilityAnimationsRemove": {},
@@ -341,7 +341,7 @@
"@displayRefreshRatePreferHighest": {},
"keepScreenOnNever": "Nigdy",
"@keepScreenOnNever": {},
- "keepScreenOnViewerOnly": "Tylko na stronie przeglądarki",
+ "keepScreenOnViewerOnly": "Na stronie przeglądarki",
"@keepScreenOnViewerOnly": {},
"videoPlaybackWithSound": "Odtwarzaj z dźwiękiem",
"@videoPlaybackWithSound": {},
@@ -359,7 +359,7 @@
"@coordinateDmsEast": {},
"coordinateDmsWest": "Z",
"@coordinateDmsWest": {},
- "unitSystemMetric": "Metryczny",
+ "unitSystemMetric": "Metryczne",
"@unitSystemMetric": {},
"videoControlsPlay": "Odtwórz",
"@videoControlsPlay": {},
@@ -375,7 +375,7 @@
"@widgetOpenPageViewer": {},
"albumTierNew": "Nowy",
"@albumTierNew": {},
- "albumTierSpecial": "Wspólny",
+ "albumTierSpecial": "Wspólne",
"@albumTierSpecial": {},
"albumTierApps": "Aplikacje",
"@albumTierApps": {},
@@ -617,7 +617,7 @@
},
"collectionEmptyFavourites": "Brak ulubionych",
"@collectionEmptyFavourites": {},
- "collectionEmptyVideos": "Brak filmów",
+ "collectionEmptyVideos": "Brak wideo",
"@collectionEmptyVideos": {},
"sortByDate": "Według daty",
"@sortByDate": {},
@@ -775,7 +775,7 @@
"@albumPickPageTitleCopy": {},
"albumPickPageTitleExport": "Wyeksportuj do albumu",
"@albumPickPageTitleExport": {},
- "tagEmpty": "Bez znaczników",
+ "tagEmpty": "Brak znaczników",
"@tagEmpty": {},
"searchCountriesSectionTitle": "Kraje",
"@searchCountriesSectionTitle": {},
@@ -1003,7 +1003,7 @@
"@settingsPrivacySectionTitle": {},
"settingsAllowInstalledAppAccess": "Zezwól na dostęp do spisu aplikacji",
"@settingsAllowInstalledAppAccess": {},
- "settingsAllowErrorReporting": "Pozwól na anonimowe zgłaszanie błędów",
+ "settingsAllowErrorReporting": "Zezwól na anonimowe zgłaszanie błędów",
"@settingsAllowErrorReporting": {},
"settingsSaveSearchHistory": "Zapisz historię wyszukiwania",
"@settingsSaveSearchHistory": {},
@@ -1045,7 +1045,7 @@
"@filePickerUseThisFolder": {},
"mapEmptyRegion": "Brak obrazów w tym regionie",
"@mapEmptyRegion": {},
- "settingsKeepScreenOnTile": "Pozostaw ekran załączony",
+ "settingsKeepScreenOnTile": "Pozostaw ekran włączony",
"@settingsKeepScreenOnTile": {},
"filePickerOpenFrom": "Otwórz z",
"@filePickerOpenFrom": {},
@@ -1057,7 +1057,7 @@
"@filePickerDoNotShowHiddenFiles": {},
"settingsActionImportDialogTitle": "Zaimportuj",
"@settingsActionImportDialogTitle": {},
- "settingsKeepScreenOnDialogTitle": "Pozostaw ekran załączony",
+ "settingsKeepScreenOnDialogTitle": "Pozostaw ekran włączony",
"@settingsKeepScreenOnDialogTitle": {},
"settingsNavigationDrawerTile": "Menu nawigacyjne",
"@settingsNavigationDrawerTile": {},
@@ -1083,7 +1083,7 @@
"@settingsVideoEnableHardwareAcceleration": {},
"settingsVideoAutoPlay": "Odtwarzaj automatycznie",
"@settingsVideoAutoPlay": {},
- "settingsSubtitleThemeSample": "To jest próbka.",
+ "settingsSubtitleThemeSample": "Przykładowy napis.",
"@settingsSubtitleThemeSample": {},
"settingsSubtitleThemeTextAlignmentDialogTitle": "Dopasowanie tekstu",
"@settingsSubtitleThemeTextAlignmentDialogTitle": {},
@@ -1131,7 +1131,7 @@
"@mapStyleTooltip": {},
"mapStyleDialogTitle": "Styl mapy",
"@mapStyleDialogTitle": {},
- "wallpaperUseScrollEffect": "Użyj efektu przewijania na ekranie głównym",
+ "wallpaperUseScrollEffect": "Używaj efektu przewijania na ekranie głównym",
"@wallpaperUseScrollEffect": {},
"tagEditorSectionRecent": "Ostatnie",
"@tagEditorSectionRecent": {},
@@ -1231,7 +1231,7 @@
"@settingsUnitSystemTile": {},
"addPathTooltip": "Dodaj ścieżkę",
"@addPathTooltip": {},
- "settingsHiddenPathsBanner": "Zdjęcia i wideo w tych folderach ani w żadnym z ich podfolderów nie pojawią się w kolekcji.",
+ "settingsHiddenPathsBanner": "Zdjęcia i wideo w tych katalogach, ani w żadnym z ich podkatalogów nie pojawią się w kolekcji.",
"@settingsHiddenPathsBanner": {},
"viewerInfoLabelOwner": "Właściciel",
"@viewerInfoLabelOwner": {},
@@ -1307,7 +1307,7 @@
"@settingsVideoButtonsTile": {},
"settingsAllowInstalledAppAccessSubtitle": "Używane do poprawy wyświetlania albumu",
"@settingsAllowInstalledAppAccessSubtitle": {},
- "settingsEnableBin": "Użyj kosza",
+ "settingsEnableBin": "Używaj kosza",
"@settingsEnableBin": {},
"settingsWidgetShowOutline": "Zarys",
"@settingsWidgetShowOutline": {},
@@ -1364,5 +1364,9 @@
"settingsSubtitleThemeTile": "Napisy",
"@settingsSubtitleThemeTile": {},
"openMapPageTooltip": "Wyświetl na mapie",
- "@openMapPageTooltip": {}
+ "@openMapPageTooltip": {},
+ "tooManyItemsErrorDialogMessage": "Spróbuj ponownie z mniejszą ilością elementów.",
+ "@tooManyItemsErrorDialogMessage": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "Przesuń palcem w górę lub w dół, aby dostosować jasność/głośność",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {}
}
diff --git a/lib/l10n/app_ro.arb b/lib/l10n/app_ro.arb
index d0ffb46ed..4fe017638 100644
--- a/lib/l10n/app_ro.arb
+++ b/lib/l10n/app_ro.arb
@@ -432,13 +432,13 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Nume",
"@renameProcessorName": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergeți acest album și articolul său?} other{Ștergeți acest album și {count} articole ale acestuia?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergi acest album și articolul din el?} other{Ștergi acest album și {count} articolele din el?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
}
},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergeți aceste albume și articolele lor?} other{Ștergeți aceste albume și {count} articole ale lor?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergi aceste albume și articolul din ele?} other{Ștergi aceste albume și {count} articolele din ele?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -1364,5 +1364,7 @@
"settingsModificationWarningDialogMessage": "Alte setări vor fi modificate.",
"@settingsModificationWarningDialogMessage": {},
"settingsDisplayUseTvInterface": "Interfață Android TV",
- "@settingsDisplayUseTvInterface": {}
+ "@settingsDisplayUseTvInterface": {},
+ "tooManyItemsErrorDialogMessage": "Încearcă din nou cu mai puține elemente.",
+ "@tooManyItemsErrorDialogMessage": {}
}
diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb
index e79a80551..25e93a4c7 100644
--- a/lib/l10n/app_th.arb
+++ b/lib/l10n/app_th.arb
@@ -569,5 +569,87 @@
"viewDialogLayoutSectionTitle": "เค้าโครง",
"@viewDialogLayoutSectionTitle": {},
"aboutLinkPolicy": "นโยบายความเป็นส่วนตัว",
- "@aboutLinkPolicy": {}
+ "@aboutLinkPolicy": {},
+ "filterLocatedLabel": "ระบุสถานที่",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "ระบุแท็ก",
+ "@filterTaggedLabel": {},
+ "keepScreenOnVideoPlayback": "ระหว่างการเล่นวิดีโอ",
+ "@keepScreenOnVideoPlayback": {},
+ "keepScreenOnViewerOnly": "หน้า Viewer เท่านั้น",
+ "@keepScreenOnViewerOnly": {},
+ "accessibilityAnimationsRemove": "ปิดการเคลื่อนไหว",
+ "@accessibilityAnimationsRemove": {},
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{ลบอัลบั้มเหล่านี้และรายการในอัลบั้มทั้งหมด?} other{ลบอัลบั้มเหล่านี้และ {count} รายการในอัลบั้ม?}}",
+ "@deleteMultiAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "entryActionShareVideoOnly": "แชร์วิดีโอเท่านั้น",
+ "@entryActionShareVideoOnly": {},
+ "entryActionShowGeoTiffOnMap": "แสดงแผนที่ซ้อน",
+ "@entryActionShowGeoTiffOnMap": {},
+ "videoActionCaptureFrame": "จับภาพเฟรม",
+ "@videoActionCaptureFrame": {},
+ "entryInfoActionRemoveLocation": "ลบสถานที่",
+ "@entryInfoActionRemoveLocation": {},
+ "filterAspectRatioLandscapeLabel": "ภาพแนวนอน",
+ "@filterAspectRatioLandscapeLabel": {},
+ "filterAspectRatioPortraitLabel": "ภาพแนวตั้ง",
+ "@filterAspectRatioPortraitLabel": {},
+ "filterNoAddressLabel": "ไม่มีที่อยู่",
+ "@filterNoAddressLabel": {},
+ "widgetOpenPageViewer": "เปิดหน้า Viewer",
+ "@widgetOpenPageViewer": {},
+ "coordinateDms": "{coordinate} {direction}",
+ "@coordinateDms": {
+ "placeholders": {
+ "coordinate": {
+ "type": "String",
+ "example": "38° 41′ 47.72″"
+ },
+ "direction": {
+ "type": "String",
+ "example": "S"
+ }
+ }
+ },
+ "missingSystemFilePickerDialogMessage": "ตัวเลือกไฟล์ระบบหายไป หรือปิดใช้งาน โปรดเปิดใช้งานและลองอีกครั้ง",
+ "@missingSystemFilePickerDialogMessage": {},
+ "addShortcutDialogLabel": "ป้ายทางลัด",
+ "@addShortcutDialogLabel": {},
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{ย้ายรายการนี้ไปยังถังขยะ?} other{ย้าย {count} รายการนี้ไปยังถังขยะ?}}",
+ "@binEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{ลบรายการนี้?} other{ลบ {count} รายการนี้?}}",
+ "@deleteEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{ลบอัลบั้มนี้และรายการในอัลบั้มทั้งหมด?} other{ลบอัลบั้มนี้และ {count} รายการในอัลบั้ม?}}",
+ "@deleteSingleAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "entryActionShareImageOnly": "แชร์รูปภาพเท่านั้น",
+ "@entryActionShareImageOnly": {},
+ "accessibilityAnimationsKeep": "เปิดการเคลื่อนไหว",
+ "@accessibilityAnimationsKeep": {},
+ "unsupportedTypeDialogMessage": "{count, plural, =1{การดำเนินการนี้ไม่รองรับรายการประเภทต่อไปนี้: {types}.} other{การดำเนินการนี้ไม่รองรับรายการประเภทต่อไปนี้: {types}.}}",
+ "@unsupportedTypeDialogMessage": {
+ "placeholders": {
+ "count": {},
+ "types": {
+ "type": "String",
+ "example": "GIF, TIFF, MP4",
+ "description": "a list of unsupported types"
+ }
+ }
+ }
}
diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb
index 84018f06c..4f1945b97 100644
--- a/lib/l10n/app_tr.arb
+++ b/lib/l10n/app_tr.arb
@@ -1208,5 +1208,7 @@
"filterTaggedLabel": "Etiketli",
"@filterTaggedLabel": {},
"tooManyItemsErrorDialogMessage": "Daha az ögeyle tekrar deneyin.",
- "@tooManyItemsErrorDialogMessage": {}
+ "@tooManyItemsErrorDialogMessage": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "Parlaklığı/ses seviyesini ayarlamak için yukarı veya aşağı kaydırın",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {}
}
diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb
index 37a0c5579..3f0f8c3d4 100644
--- a/lib/l10n/app_uk.arb
+++ b/lib/l10n/app_uk.arb
@@ -1364,5 +1364,9 @@
"filterLocatedLabel": "Розташований",
"@filterLocatedLabel": {},
"filterTaggedLabel": "Позначений тегом",
- "@filterTaggedLabel": {}
+ "@filterTaggedLabel": {},
+ "tooManyItemsErrorDialogMessage": "Спробуйте ще раз з меншою кількістю елементів.",
+ "@tooManyItemsErrorDialogMessage": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "Проведіть пальцем угору або вниз, щоб налаштувати яскравість/гучність",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {}
}
diff --git a/lib/model/entry_metadata_edition.dart b/lib/model/entry_metadata_edition.dart
index 7aa91fae6..cd592f02b 100644
--- a/lib/model/entry_metadata_edition.dart
+++ b/lib/model/entry_metadata_edition.dart
@@ -463,7 +463,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
modified |= XMP.removeElements(
descriptions,
XMP.containerDirectory,
- Namespaces.container,
+ Namespaces.gContainer,
);
modified |= [
diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart
index 1dbcc1a18..fb2273e6d 100644
--- a/lib/model/settings/defaults.dart
+++ b/lib/model/settings/defaults.dart
@@ -97,6 +97,7 @@ class SettingsDefaults {
static const videoControls = VideoControls.play;
static const videoGestureDoubleTapTogglePlay = false;
static const videoGestureSideDoubleTapSeek = true;
+ static const videoGestureVerticalDragBrightnessVolume = false;
// subtitles
static const subtitleFontSize = 20.0;
diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart
index 27c944d82..f2595ffae 100644
--- a/lib/model/settings/settings.dart
+++ b/lib/model/settings/settings.dart
@@ -41,7 +41,6 @@ class Settings extends ChangeNotifier {
static const Set _internalKeys = {
hasAcceptedTermsKey,
catalogTimeZoneKey,
- videoShowRawTimedTextKey,
searchHistoryKey,
platformAccelerometerRotationKey,
platformTransitionAnimationScaleKey,
@@ -131,10 +130,10 @@ class Settings extends ChangeNotifier {
static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec';
static const videoAutoPlayModeKey = 'video_auto_play_mode';
static const videoLoopModeKey = 'video_loop';
- static const videoShowRawTimedTextKey = 'video_show_raw_timed_text';
static const videoControlsKey = 'video_controls';
static const videoGestureDoubleTapTogglePlayKey = 'video_gesture_double_tap_toggle_play';
static const videoGestureSideDoubleTapSeekKey = 'video_gesture_side_double_tap_skip';
+ static const videoGestureVerticalDragBrightnessVolumeKey = 'video_gesture_vertical_drag_brightness_volume';
// subtitles
static const subtitleFontSizeKey = 'subtitle_font_size';
@@ -637,10 +636,6 @@ class Settings extends ChangeNotifier {
set videoLoopMode(VideoLoopMode newValue) => _set(videoLoopModeKey, newValue.toString());
- bool get videoShowRawTimedText => getBool(videoShowRawTimedTextKey) ?? SettingsDefaults.videoShowRawTimedText;
-
- set videoShowRawTimedText(bool newValue) => _set(videoShowRawTimedTextKey, newValue);
-
VideoControls get videoControls => getEnumOrDefault(videoControlsKey, SettingsDefaults.videoControls, VideoControls.values);
set videoControls(VideoControls newValue) => _set(videoControlsKey, newValue.toString());
@@ -653,6 +648,10 @@ class Settings extends ChangeNotifier {
set videoGestureSideDoubleTapSeek(bool newValue) => _set(videoGestureSideDoubleTapSeekKey, newValue);
+ bool get videoGestureVerticalDragBrightnessVolume => getBool(videoGestureVerticalDragBrightnessVolumeKey) ?? SettingsDefaults.videoGestureVerticalDragBrightnessVolume;
+
+ set videoGestureVerticalDragBrightnessVolume(bool newValue) => _set(videoGestureVerticalDragBrightnessVolumeKey, newValue);
+
// subtitles
double get subtitleFontSize => getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize;
@@ -1039,6 +1038,7 @@ class Settings extends ChangeNotifier {
case enableVideoHardwareAccelerationKey:
case videoGestureDoubleTapTogglePlayKey:
case videoGestureSideDoubleTapSeekKey:
+ case videoGestureVerticalDragBrightnessVolumeKey:
case subtitleShowOutlineKey:
case tagEditorCurrentFilterSectionExpandedKey:
case saveSearchHistoryKey:
diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart
index 50185d2b2..cd05e0c89 100644
--- a/lib/theme/icons.dart
+++ b/lib/theme/icons.dart
@@ -14,6 +14,8 @@ class AIcons {
static const IconData aspectRatio = Icons.aspect_ratio_outlined;
static const IconData bin = Icons.delete_outlined;
static const IconData broken = Icons.broken_image_outlined;
+ static const IconData brightnessMin = Icons.brightness_low_outlined;
+ static const IconData brightnessMax = Icons.brightness_high_outlined;
static const IconData checked = Icons.done_outlined;
static const IconData count = MdiIcons.counter;
static const IconData counter = Icons.plus_one_outlined;
@@ -52,6 +54,8 @@ class AIcons {
static const IconData text = Icons.format_quote_outlined;
static const IconData tag = Icons.local_offer_outlined;
static const IconData tagUntagged = MdiIcons.tagOffOutline;
+ static const IconData volumeMin = Icons.volume_mute_outlined;
+ static const IconData volumeMax = Icons.volume_up_outlined;
// view
static const IconData group = Icons.group_work_outlined;
diff --git a/lib/utils/dependencies.dart b/lib/utils/dependencies.dart
index 9bb4babd0..27a0c94c9 100644
--- a/lib/utils/dependencies.dart
+++ b/lib/utils/dependencies.dart
@@ -123,6 +123,11 @@ class Dependencies {
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE',
sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher',
),
+ Dependency(
+ name: 'Volume Controller',
+ license: mit,
+ sourceUrl: 'https://github.com/kurenai7968/volume_controller',
+ ),
];
static const List _googleMobileServices = [
diff --git a/lib/utils/xmp_utils.dart b/lib/utils/xmp_utils.dart
index 72f2b3515..f660e51d6 100644
--- a/lib/utils/xmp_utils.dart
+++ b/lib/utils/xmp_utils.dart
@@ -7,7 +7,6 @@ class Namespaces {
static const avm = 'http://www.communicatingastronomy.org/avm/1.0/';
static const camera = 'http://pix4d.com/camera/1.0/';
static const cc = 'http://creativecommons.org/ns#';
- static const container = 'http://ns.google.com/photos/1.0/container/';
static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/';
static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/';
static const crlcp = 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/';
@@ -26,9 +25,13 @@ class Namespaces {
static const exifEx = 'http://cipa.jp/exif/1.0/';
static const gAudio = 'http://ns.google.com/photos/1.0/audio/';
static const gCamera = 'http://ns.google.com/photos/1.0/camera/';
+ static const gContainer = 'http://ns.google.com/photos/1.0/container/';
static const gCreations = 'http://ns.google.com/photos/1.0/creations/';
static const gDepth = 'http://ns.google.com/photos/1.0/depthmap/';
static const gDevice = 'http://ns.google.com/photos/dd/1.0/device/';
+ static const gDeviceCamera = 'http://ns.google.com/photos/dd/1.0/camera/';
+ static const gDeviceContainer = 'http://ns.google.com/photos/dd/1.0/container/';
+ static const gDeviceItem = 'http://ns.google.com/photos/dd/1.0/item/';
static const gFocus = 'http://ns.google.com/photos/1.0/focus/';
static const gImage = 'http://ns.google.com/photos/1.0/image/';
static const gPano = 'http://ns.google.com/photos/1.0/panorama/';
@@ -83,7 +86,6 @@ class Namespaces {
avm: 'Astronomy Visualization',
camera: 'Pix4D Camera',
cc: 'Creative Commons',
- container: 'Container',
crd: 'Camera Raw Defaults',
creatorAtom: 'After Effects',
crs: 'Camera Raw Settings',
@@ -97,6 +99,7 @@ class Namespaces {
exifEx: 'Exif Ex',
gAudio: 'Google Audio',
gCamera: 'Google Camera',
+ gContainer: 'Google Container',
gCreations: 'Google Creations',
gDepth: 'Google Depth',
gDevice: 'Google Device',
@@ -138,7 +141,7 @@ class Namespaces {
};
static final defaultPrefixes = {
- container: 'Container',
+ gContainer: 'Container',
dc: 'dc',
gCamera: 'GCamera',
microsoftPhoto: 'MicrosoftPhoto',
diff --git a/lib/widgets/about/about_page.dart b/lib/widgets/about/about_page.dart
index c5c1e654b..4888d01bc 100644
--- a/lib/widgets/about/about_page.dart
+++ b/lib/widgets/about/about_page.dart
@@ -5,6 +5,7 @@ import 'package:aves/widgets/about/credits.dart';
import 'package:aves/widgets/about/licenses.dart';
import 'package:aves/widgets/about/translators.dart';
import 'package:aves/widgets/common/basic/insets.dart';
+import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@@ -28,7 +29,8 @@ class AboutPage extends StatelessWidget {
sliver: SliverList(
delegate: SliverChildListDelegate(
[
- AppReference(showLogo: !useTvLayout),
+ const TvEdgeFocus(),
+ const AppReference(),
if (!settings.useTvLayout) ...[
const Divider(),
const BugReport(),
diff --git a/lib/widgets/about/app_ref.dart b/lib/widgets/about/app_ref.dart
index ce4a94ba4..fbab83d89 100644
--- a/lib/widgets/about/app_ref.dart
+++ b/lib/widgets/about/app_ref.dart
@@ -10,12 +10,7 @@ import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
class AppReference extends StatefulWidget {
- final bool showLogo;
-
- const AppReference({
- super.key,
- required this.showLogo,
- });
+ const AppReference({super.key});
@override
State createState() => _AppReferenceState();
@@ -24,6 +19,13 @@ class AppReference extends StatefulWidget {
class _AppReferenceState extends State {
late Future _packageInfoLoader;
+ static const _appTitleStyle = TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.normal,
+ letterSpacing: 1.0,
+ fontFeatures: [FontFeature.enable('smcp')],
+ );
+
@override
void initState() {
super.initState();
@@ -44,28 +46,19 @@ class _AppReferenceState extends State {
}
Widget _buildAvesLine() {
- const style = TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.normal,
- letterSpacing: 1.0,
- fontFeatures: [FontFeature.enable('smcp')],
- );
-
return FutureBuilder(
future: _packageInfoLoader,
builder: (context, snapshot) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
- if (widget.showLogo) ...[
- AvesLogo(
- size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
- ),
- const SizedBox(width: 8),
- ],
+ AvesLogo(
+ size: _appTitleStyle.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
+ ),
+ const SizedBox(width: 8),
Text(
'${context.l10n.appName} ${snapshot.data?.version}',
- style: style,
+ style: _appTitleStyle,
),
],
);
diff --git a/lib/widgets/about/credits.dart b/lib/widgets/about/credits.dart
index e4422cce3..2fd508712 100644
--- a/lib/widgets/about/credits.dart
+++ b/lib/widgets/about/credits.dart
@@ -1,4 +1,4 @@
-import 'package:aves/utils/constants.dart';
+import 'package:aves/widgets/about/title.dart';
import 'package:aves/widgets/common/basic/link_chip.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/material.dart';
@@ -14,13 +14,7 @@ class AboutCredits extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- ConstrainedBox(
- constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
- child: Align(
- alignment: AlignmentDirectional.centerStart,
- child: Text(l10n.aboutCreditsSectionTitle, style: Constants.knownTitleTextStyle),
- ),
- ),
+ AboutSectionTitle(text: l10n.aboutCreditsSectionTitle),
const SizedBox(height: 8),
Text.rich(
TextSpan(
diff --git a/lib/widgets/about/licenses.dart b/lib/widgets/about/licenses.dart
index 813776474..50b376ce3 100644
--- a/lib/widgets/about/licenses.dart
+++ b/lib/widgets/about/licenses.dart
@@ -1,8 +1,9 @@
import 'package:aves/app_flavor.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/ref/brand_colors.dart';
import 'package:aves/theme/colors.dart';
-import 'package:aves/utils/constants.dart';
import 'package:aves/utils/dependencies.dart';
+import 'package:aves/widgets/about/title.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_expansion_tile.dart';
@@ -50,30 +51,32 @@ class _LicensesState extends State {
[
_buildHeader(),
const SizedBox(height: 16),
- AvesExpansionTile(
- title: context.l10n.aboutLicensesAndroidLibrariesSectionTitle,
- highlightColor: colors.fromBrandColor(BrandColors.android),
- expandedNotifier: _expandedNotifier,
- children: _platform.map((package) => LicenseRow(package: package)).toList(),
- ),
- AvesExpansionTile(
- title: context.l10n.aboutLicensesFlutterPluginsSectionTitle,
- highlightColor: colors.fromBrandColor(BrandColors.flutter),
- expandedNotifier: _expandedNotifier,
- children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
- ),
- AvesExpansionTile(
- title: context.l10n.aboutLicensesFlutterPackagesSectionTitle,
- highlightColor: colors.fromBrandColor(BrandColors.flutter),
- expandedNotifier: _expandedNotifier,
- children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
- ),
- AvesExpansionTile(
- title: context.l10n.aboutLicensesDartPackagesSectionTitle,
- highlightColor: colors.fromBrandColor(BrandColors.flutter),
- expandedNotifier: _expandedNotifier,
- children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
- ),
+ if (!settings.useTvLayout) ...[
+ AvesExpansionTile(
+ title: context.l10n.aboutLicensesAndroidLibrariesSectionTitle,
+ highlightColor: colors.fromBrandColor(BrandColors.android),
+ expandedNotifier: _expandedNotifier,
+ children: _platform.map((package) => LicenseRow(package: package)).toList(),
+ ),
+ AvesExpansionTile(
+ title: context.l10n.aboutLicensesFlutterPluginsSectionTitle,
+ highlightColor: colors.fromBrandColor(BrandColors.flutter),
+ expandedNotifier: _expandedNotifier,
+ children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
+ ),
+ AvesExpansionTile(
+ title: context.l10n.aboutLicensesFlutterPackagesSectionTitle,
+ highlightColor: colors.fromBrandColor(BrandColors.flutter),
+ expandedNotifier: _expandedNotifier,
+ children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
+ ),
+ AvesExpansionTile(
+ title: context.l10n.aboutLicensesDartPackagesSectionTitle,
+ highlightColor: colors.fromBrandColor(BrandColors.flutter),
+ expandedNotifier: _expandedNotifier,
+ children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
+ ),
+ ],
Center(
child: AvesOutlinedButton(
label: context.l10n.aboutLicensesShowAllButtonLabel,
@@ -104,13 +107,7 @@ class _LicensesState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- ConstrainedBox(
- constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
- child: Align(
- alignment: AlignmentDirectional.centerStart,
- child: Text(context.l10n.aboutLicensesSectionTitle, style: Constants.knownTitleTextStyle),
- ),
- ),
+ AboutSectionTitle(text: context.l10n.aboutLicensesSectionTitle),
const SizedBox(height: 8),
Text(context.l10n.aboutLicensesBanner),
],
diff --git a/lib/widgets/about/policy_page.dart b/lib/widgets/about/policy_page.dart
index ea4a1d113..3803bae2f 100644
--- a/lib/widgets/about/policy_page.dart
+++ b/lib/widgets/about/policy_page.dart
@@ -1,3 +1,4 @@
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/basic/markdown_container.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/material.dart';
@@ -14,6 +15,7 @@ class PolicyPage extends StatefulWidget {
class _PolicyPageState extends State {
late Future _termsLoader;
+ final ScrollController _scrollController = ScrollController();
static const termsPath = 'assets/terms.md';
static const termsDirection = TextDirection.ltr;
@@ -28,26 +30,72 @@ class _PolicyPageState extends State {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
+ automaticallyImplyLeading: !settings.useTvLayout,
title: Text(context.l10n.policyPageTitle),
),
body: SafeArea(
- child: Center(
- child: FutureBuilder(
- future: _termsLoader,
- builder: (context, snapshot) {
- if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
- final terms = snapshot.data!;
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: MarkdownContainer(
- data: terms,
- textDirection: termsDirection,
- ),
- );
- },
+ child: FocusableActionDetector(
+ autofocus: true,
+ shortcuts: const {
+ SingleActivator(LogicalKeyboardKey.arrowUp): _ScrollIntent.up(),
+ SingleActivator(LogicalKeyboardKey.arrowDown): _ScrollIntent.down(),
+ },
+ actions: {
+ _ScrollIntent: CallbackAction<_ScrollIntent>(onInvoke: _onScrollIntent),
+ },
+ child: Center(
+ child: FutureBuilder(
+ future: _termsLoader,
+ builder: (context, snapshot) {
+ if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
+ final terms = snapshot.data!;
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: MarkdownContainer(
+ scrollController: _scrollController,
+ data: terms,
+ textDirection: termsDirection,
+ ),
+ );
+ },
+ ),
),
),
),
);
}
+
+ void _onScrollIntent(_ScrollIntent intent) {
+ late int factor;
+ switch (intent.type) {
+ case _ScrollDirection.up:
+ factor = -1;
+ break;
+ case _ScrollDirection.down:
+ factor = 1;
+ break;
+ }
+ _scrollController.animateTo(
+ _scrollController.offset + factor * 150,
+ duration: const Duration(milliseconds: 500),
+ curve: Curves.easeOutCubic,
+ );
+ }
+}
+
+class _ScrollIntent extends Intent {
+ const _ScrollIntent({
+ required this.type,
+ });
+
+ const _ScrollIntent.up() : type = _ScrollDirection.up;
+
+ const _ScrollIntent.down() : type = _ScrollDirection.down;
+
+ final _ScrollDirection type;
+}
+
+enum _ScrollDirection {
+ up,
+ down,
}
diff --git a/lib/widgets/about/title.dart b/lib/widgets/about/title.dart
new file mode 100644
index 000000000..bfb63b27d
--- /dev/null
+++ b/lib/widgets/about/title.dart
@@ -0,0 +1,37 @@
+import 'package:aves/model/settings/settings.dart';
+import 'package:aves/utils/constants.dart';
+import 'package:flutter/material.dart';
+
+class AboutSectionTitle extends StatelessWidget {
+ final String text;
+
+ const AboutSectionTitle({
+ super.key,
+ required this.text,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ Widget child = Container(
+ alignment: AlignmentDirectional.centerStart,
+ constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
+ child: Text(text, style: Constants.knownTitleTextStyle),
+ );
+
+ if (settings.useTvLayout) {
+ child = InkWell(
+ borderRadius: const BorderRadius.all(Radius.circular(123)),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ child,
+ ],
+ ),
+ ),
+ );
+ }
+ return child;
+ }
+}
diff --git a/lib/widgets/about/translators.dart b/lib/widgets/about/translators.dart
index d83094991..7d172bbaa 100644
--- a/lib/widgets/about/translators.dart
+++ b/lib/widgets/about/translators.dart
@@ -1,6 +1,7 @@
import 'dart:math';
import 'package:aves/utils/constants.dart';
+import 'package:aves/widgets/about/title.dart';
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:collection/collection.dart';
@@ -50,27 +51,21 @@ class AboutTranslators extends StatelessWidget {
// Contributor('slasb37', 'p84haghi@gmail.com'), // Persian
// Contributor('tryvseu', 'tryvseu@tuta.io'), // Nynorsk
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
+ // Contributor('Idj', 'joneltmp+goahn@gmail.com'), // Hebrew
};
@override
Widget build(BuildContext context) {
- final l10n = context.l10n;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- ConstrainedBox(
- constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
- child: Align(
- alignment: AlignmentDirectional.centerStart,
- child: Text(l10n.aboutTranslatorsSectionTitle, style: Constants.knownTitleTextStyle),
- ),
- ),
+ AboutSectionTitle(text: context.l10n.aboutTranslatorsSectionTitle),
const SizedBox(height: 8),
_RandomTextSpanHighlighter(
spans: translators.map((v) => v.name).toList(),
- highlightColor: Theme.of(context).colorScheme.onPrimary,
+ color: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(height: 16),
],
@@ -81,11 +76,11 @@ class AboutTranslators extends StatelessWidget {
class _RandomTextSpanHighlighter extends StatefulWidget {
final List spans;
- final Color highlightColor;
+ final Color color;
const _RandomTextSpanHighlighter({
required this.spans,
- required this.highlightColor,
+ required this.color,
});
@override
@@ -102,18 +97,21 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
void initState() {
super.initState();
+ final color = widget.color;
_baseStyle = TextStyle(
+ color: color.withOpacity(.7),
shadows: [
Shadow(
- color: widget.highlightColor.withOpacity(0),
+ color: color.withOpacity(0),
blurRadius: 0,
)
],
);
final highlightStyle = TextStyle(
+ color: color.withOpacity(1),
shadows: [
Shadow(
- color: widget.highlightColor,
+ color: color.withOpacity(1),
blurRadius: 3,
)
],
@@ -132,7 +130,7 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
..repeat(reverse: true);
_animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(CurvedAnimation(
parent: _controller,
- curve: Curves.linear,
+ curve: Curves.easeInOutCubic,
));
}
diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart
index 445fae01c..8bcdf131b 100644
--- a/lib/widgets/aves_app.dart
+++ b/lib/widgets/aves_app.dart
@@ -54,7 +54,7 @@ class AvesApp extends StatefulWidget {
final AppFlavor flavor;
// temporary exclude locales not ready yet for prime time
- static final _unsupportedLocales = {'ar', 'fa', 'gl', 'nn', 'th'}.map(Locale.new).toSet();
+ static final _unsupportedLocales = {'ar', 'fa', 'gl', 'he', 'nn', 'th'}.map(Locale.new).toSet();
static final List supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList();
static final ValueNotifier cutoutInsetsNotifier = ValueNotifier(EdgeInsets.zero);
static final GlobalKey navigatorKey = GlobalKey(debugLabel: 'app-navigator');
@@ -540,6 +540,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
? 'profile'
: 'debug',
'has_mobile_services': mobileServices.isServiceAvailable,
+ 'is_television': device.isTelevision,
'locales': WidgetsBinding.instance.window.locales.join(', '),
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
});
diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart
index 3198fd9f1..e23286a9b 100644
--- a/lib/widgets/collection/entry_set_action_delegate.dart
+++ b/lib/widgets/collection/entry_set_action_delegate.dart
@@ -58,6 +58,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
}) {
final canWrite = !settings.isReadOnly;
final isMain = appMode == AppMode.main;
+ final useTvLayout = settings.useTvLayout;
switch (action) {
// general
case EntrySetAction.configureView:
@@ -70,9 +71,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
return isSelecting && selectedItemCount == itemCount;
// browsing
case EntrySetAction.searchCollection:
- return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
+ return !useTvLayout && appMode.canNavigate && !isSelecting;
case EntrySetAction.toggleTitleSearch:
- return !isSelecting;
+ return !useTvLayout && !isSelecting;
case EntrySetAction.addShortcut:
return isMain && !isSelecting && device.canPinShortcut && !isTrash;
case EntrySetAction.emptyBin:
@@ -83,7 +84,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.stats:
return isMain;
case EntrySetAction.rescan:
- return !settings.useTvLayout && isMain && !isTrash;
+ return !useTvLayout && isMain && !isTrash;
// selecting
case EntrySetAction.share:
case EntrySetAction.toggleFavourite:
diff --git a/lib/widgets/common/action_mixins/entry_storage.dart b/lib/widgets/common/action_mixins/entry_storage.dart
index e99c52296..defc70ecf 100644
--- a/lib/widgets/common/action_mixins/entry_storage.dart
+++ b/lib/widgets/common/action_mixins/entry_storage.dart
@@ -27,7 +27,7 @@ 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_confirmation_dialog.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
-import 'package:aves/widgets/filter_grids/album_pick.dart';
+import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
import 'package:aves/widgets/viewer/notifications.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
diff --git a/lib/widgets/common/basic/markdown_container.dart b/lib/widgets/common/basic/markdown_container.dart
index a01fd3cb6..a8174795b 100644
--- a/lib/widgets/common/basic/markdown_container.dart
+++ b/lib/widgets/common/basic/markdown_container.dart
@@ -6,11 +6,13 @@ import 'package:flutter_markdown/flutter_markdown.dart';
class MarkdownContainer extends StatelessWidget {
final String data;
final TextDirection? textDirection;
+ final ScrollController? scrollController;
const MarkdownContainer({
super.key,
required this.data,
this.textDirection,
+ this.scrollController,
});
static const double maxWidth = 460;
@@ -44,6 +46,7 @@ class MarkdownContainer extends StatelessWidget {
data: data,
selectable: true,
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
+ controller: scrollController,
shrinkWrap: true,
),
),
diff --git a/lib/widgets/common/basic/tv_edge_focus.dart b/lib/widgets/common/basic/tv_edge_focus.dart
new file mode 100644
index 000000000..9e624a030
--- /dev/null
+++ b/lib/widgets/common/basic/tv_edge_focus.dart
@@ -0,0 +1,25 @@
+import 'package:aves/model/settings/settings.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+// to be placed at the edges of lists and grids,
+// so that TV can reach them with D-pad
+class TvEdgeFocus extends StatelessWidget {
+ final FocusNode? focusNode;
+
+ const TvEdgeFocus({
+ super.key,
+ this.focusNode,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final useTvLayout = context.select((s) => s.useTvLayout);
+ return useTvLayout
+ ? Focus(
+ focusNode: focusNode,
+ child: const SizedBox(),
+ )
+ : const SizedBox();
+ }
+}
diff --git a/lib/widgets/common/expandable_filter_row.dart b/lib/widgets/common/expandable_filter_row.dart
index 54d0ebf63..3557e651c 100644
--- a/lib/widgets/common/expandable_filter_row.dart
+++ b/lib/widgets/common/expandable_filter_row.dart
@@ -1,4 +1,5 @@
import 'package:aves/model/filters/filters.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
@@ -31,26 +32,49 @@ class TitledExpandableFilterRow extends StatelessWidget {
final isExpanded = expandedNotifier.value == title;
+ Widget header = Text(
+ title,
+ style: Constants.knownTitleTextStyle,
+ );
+ void toggle() => expandedNotifier.value = isExpanded ? null : title;
+ if (settings.useTvLayout) {
+ header = Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: InkWell(
+ onTap: toggle,
+ borderRadius: const BorderRadius.all(Radius.circular(123)),
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ header,
+ ],
+ ),
+ ),
+ ),
+ );
+ } else {
+ header = Padding(
+ padding: const EdgeInsets.all(16),
+ child: Row(
+ children: [
+ header,
+ const Spacer(),
+ IconButton(
+ icon: Icon(isExpanded ? AIcons.collapse : AIcons.expand),
+ onPressed: toggle,
+ tooltip: isExpanded ? MaterialLocalizations.of(context).expandedIconTapHint : MaterialLocalizations.of(context).collapsedIconTapHint,
+ ),
+ ],
+ ),
+ );
+ }
+
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Padding(
- padding: const EdgeInsets.all(16),
- child: Row(
- children: [
- Text(
- title,
- style: Constants.knownTitleTextStyle,
- ),
- const Spacer(),
- IconButton(
- icon: Icon(isExpanded ? AIcons.collapse : AIcons.expand),
- onPressed: () => expandedNotifier.value = isExpanded ? null : title,
- tooltip: isExpanded ? MaterialLocalizations.of(context).expandedIconTapHint : MaterialLocalizations.of(context).collapsedIconTapHint,
- ),
- ],
- ),
- ),
+ header,
ExpandableFilterRow(
filters: filters,
isExpanded: isExpanded,
diff --git a/lib/widgets/common/grid/header.dart b/lib/widgets/common/grid/header.dart
index bc941d63b..d6da38773 100644
--- a/lib/widgets/common/grid/header.dart
+++ b/lib/widgets/common/grid/header.dart
@@ -34,18 +34,10 @@ class SectionHeader extends StatelessWidget {
Widget build(BuildContext context) {
Widget child = _buildContent(context);
if (settings.useTvLayout) {
- final primaryColor = Theme.of(context).colorScheme.primary;
- child = Material(
- type: MaterialType.transparency,
- child: InkResponse(
- onTap: _onTap(context),
- containedInkWell: true,
- highlightShape: BoxShape.rectangle,
- borderRadius: const BorderRadius.all(Radius.circular(123)),
- hoverColor: primaryColor.withOpacity(0.04),
- splashColor: primaryColor.withOpacity(0.12),
- child: child,
- ),
+ child = InkWell(
+ onTap: _onTap(context),
+ borderRadius: const BorderRadius.all(Radius.circular(123)),
+ child: child,
);
}
return Container(
diff --git a/lib/widgets/common/identity/buttons/captioned_button.dart b/lib/widgets/common/identity/buttons/captioned_button.dart
index dda3e7787..10cc09602 100644
--- a/lib/widgets/common/identity/buttons/captioned_button.dart
+++ b/lib/widgets/common/identity/buttons/captioned_button.dart
@@ -8,7 +8,7 @@ class CaptionedButton extends StatefulWidget {
final Animation scale;
final Widget captionText;
final CaptionedIconButtonBuilder iconButtonBuilder;
- final bool showCaption;
+ final bool autofocus, showCaption;
final VoidCallback? onPressed;
static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8);
@@ -21,6 +21,7 @@ class CaptionedButton extends StatefulWidget {
CaptionedIconButtonBuilder? iconButtonBuilder,
String? caption,
Widget? captionText,
+ this.autofocus = false,
this.showCaption = true,
required this.onPressed,
}) : assert(icon != null || iconButtonBuilder != null),
@@ -57,6 +58,7 @@ class CaptionedButton extends StatefulWidget {
class _CaptionedButtonState extends State {
final FocusNode _focusNode = FocusNode();
final ValueNotifier _focusedNotifier = ValueNotifier(false);
+ bool _didAutofocus = false;
@override
void initState() {
@@ -65,12 +67,21 @@ class _CaptionedButtonState extends State {
_focusNode.addListener(_onFocusChanged);
}
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _handleAutofocus();
+ }
+
@override
void didUpdateWidget(covariant CaptionedButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.onPressed != widget.onPressed) {
_updateTraversal();
}
+ if (oldWidget.autofocus != widget.autofocus) {
+ _handleAutofocus();
+ }
}
@override
@@ -120,6 +131,13 @@ class _CaptionedButtonState extends State {
);
}
+ void _handleAutofocus() {
+ if (!_didAutofocus && widget.autofocus) {
+ FocusScope.of(context).autofocus(_focusNode);
+ _didAutofocus = true;
+ }
+ }
+
void _onFocusChanged() => _focusedNotifier.value = _focusNode.hasFocus;
void _updateTraversal() {
diff --git a/lib/widgets/common/search/delegate.dart b/lib/widgets/common/search/delegate.dart
index 282c38aa1..26dd06c57 100644
--- a/lib/widgets/common/search/delegate.dart
+++ b/lib/widgets/common/search/delegate.dart
@@ -18,6 +18,9 @@ abstract class AvesSearchDelegate extends SearchDelegate {
query = initialQuery ?? '';
}
+ @mustCallSuper
+ void dispose() {}
+
@override
Widget? buildLeading(BuildContext context) {
if (settings.useTvLayout) {
@@ -44,7 +47,7 @@ abstract class AvesSearchDelegate extends SearchDelegate {
@override
List? buildActions(BuildContext context) {
return [
- if (query.isNotEmpty)
+ if (!settings.useTvLayout && query.isNotEmpty)
IconButton(
icon: const Icon(AIcons.clear),
onPressed: () {
@@ -63,28 +66,40 @@ abstract class AvesSearchDelegate extends SearchDelegate {
void clean() {
currentBody = null;
- focusNode?.unfocus();
+ searchFieldFocusNode?.unfocus();
}
// adapted from Flutter `SearchDelegate` in `/material/search.dart`
@override
void showResults(BuildContext context) {
- focusNode?.unfocus();
- currentBody = SearchBody.results;
+ if (settings.useTvLayout) {
+ suggestionsScrollController?.jumpTo(0);
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ suggestionsFocusNode?.requestFocus();
+ FocusScope.of(context).nextFocus();
+ });
+ } else {
+ searchFieldFocusNode?.unfocus();
+ currentBody = SearchBody.results;
+ }
}
@override
void showSuggestions(BuildContext context) {
- assert(focusNode != null, '_focusNode must be set by route before showSuggestions is called.');
- focusNode!.requestFocus();
+ assert(searchFieldFocusNode != null, '_focusNode must be set by route before showSuggestions is called.');
+ searchFieldFocusNode!.requestFocus();
currentBody = SearchBody.suggestions;
}
@override
Animation get transitionAnimation => proxyAnimation;
- FocusNode? focusNode;
+ FocusNode? searchFieldFocusNode;
+
+ FocusNode? get suggestionsFocusNode => null;
+
+ ScrollController? get suggestionsScrollController => null;
final TextEditingController queryTextController = TextEditingController();
diff --git a/lib/widgets/common/search/page.dart b/lib/widgets/common/search/page.dart
index 4e31065fb..58ebf7e9d 100644
--- a/lib/widgets/common/search/page.dart
+++ b/lib/widgets/common/search/page.dart
@@ -29,7 +29,7 @@ class SearchPage extends StatefulWidget {
class _SearchPageState extends State {
final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay);
- final FocusNode _focusNode = FocusNode();
+ final FocusNode _searchFieldFocusNode = FocusNode();
final DoubleBackPopHandler _doubleBackPopHandler = DoubleBackPopHandler();
@override
@@ -37,7 +37,7 @@ class _SearchPageState extends State {
super.initState();
_registerWidget(widget);
widget.animation.addStatusListener(_onAnimationStatusChanged);
- _focusNode.addListener(_onFocusChanged);
+ _searchFieldFocusNode.addListener(_onFocusChanged);
}
@override
@@ -53,21 +53,22 @@ class _SearchPageState extends State {
void dispose() {
_unregisterWidget(widget);
widget.animation.removeStatusListener(_onAnimationStatusChanged);
- _focusNode.dispose();
+ _searchFieldFocusNode.dispose();
_doubleBackPopHandler.dispose();
+ widget.delegate.dispose();
super.dispose();
}
void _registerWidget(SearchPage widget) {
widget.delegate.queryTextController.addListener(_onQueryChanged);
widget.delegate.currentBodyNotifier.addListener(_onSearchBodyChanged);
- widget.delegate.focusNode = _focusNode;
+ widget.delegate.searchFieldFocusNode = _searchFieldFocusNode;
}
void _unregisterWidget(SearchPage widget) {
widget.delegate.queryTextController.removeListener(_onQueryChanged);
widget.delegate.currentBodyNotifier.removeListener(_onSearchBodyChanged);
- widget.delegate.focusNode = null;
+ widget.delegate.searchFieldFocusNode = null;
}
void _onAnimationStatusChanged(AnimationStatus status) {
@@ -77,12 +78,12 @@ class _SearchPageState extends State {
widget.animation.removeStatusListener(_onAnimationStatusChanged);
Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) {
if (!mounted) return;
- _focusNode.requestFocus();
+ _searchFieldFocusNode.requestFocus();
});
}
void _onFocusChanged() {
- if (_focusNode.hasFocus && widget.delegate.currentBody != SearchBody.suggestions) {
+ if (_searchFieldFocusNode.hasFocus && widget.delegate.currentBody != SearchBody.suggestions) {
widget.delegate.showSuggestions(context);
}
}
@@ -136,7 +137,7 @@ class _SearchPageState extends State {
style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
child: TextField(
controller: widget.delegate.queryTextController,
- focusNode: _focusNode,
+ focusNode: _searchFieldFocusNode,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.delegate.searchFieldLabel,
diff --git a/lib/widgets/debug/settings.dart b/lib/widgets/debug/settings.dart
index a4bbe7c98..d4fc6dc75 100644
--- a/lib/widgets/debug/settings.dart
+++ b/lib/widgets/debug/settings.dart
@@ -43,11 +43,6 @@ class DebugSettingsSection extends StatelessWidget {
onChanged: (v) => settings.canUseAnalysisService = v,
title: const Text('canUseAnalysisService'),
),
- SwitchListTile(
- value: settings.videoShowRawTimedText,
- onChanged: (v) => settings.videoShowRawTimedText = v,
- title: const Text('videoShowRawTimedText'),
- ),
Padding(
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
child: InfoRowGroup(
diff --git a/lib/widgets/dialogs/aves_selection_dialog.dart b/lib/widgets/dialogs/aves_selection_dialog.dart
index 95352649d..836d8f48a 100644
--- a/lib/widgets/dialogs/aves_selection_dialog.dart
+++ b/lib/widgets/dialogs/aves_selection_dialog.dart
@@ -135,16 +135,14 @@ class SelectionRadioListTile extends StatelessWidget {
reselectable: true,
title: Text(
title,
- softWrap: false,
- overflow: TextOverflow.fade,
- maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ maxLines: 2,
),
subtitle: subtitle != null
? Text(
subtitle,
softWrap: false,
overflow: TextOverflow.fade,
- maxLines: 1,
)
: null,
dense: dense,
diff --git a/lib/widgets/filter_grids/album_pick.dart b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart
similarity index 79%
rename from lib/widgets/filter_grids/album_pick.dart
rename to lib/widgets/dialogs/pick_dialogs/album_pick_page.dart
index 3a19327cd..7039ccfe4 100644
--- a/lib/widgets/filter_grids/album_pick.dart
+++ b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart
@@ -13,6 +13,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu.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/buttons/captioned_button.dart';
import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/common/providers/selection_provider.dart';
import 'package:aves/widgets/dialogs/filter_editors/create_album_dialog.dart';
@@ -128,6 +129,53 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
Selection> selection,
AlbumChipSetActionDelegate actionDelegate,
) {
+ final itemCount = actionDelegate.allItems.length;
+ final isSelecting = selection.isSelecting;
+ final selectedItems = selection.selectedItems;
+ final selectedFilters = selectedItems.map((v) => v.filter).toSet();
+
+ bool isVisible(ChipSetAction action) => actionDelegate.isVisible(
+ action,
+ appMode: appMode,
+ isSelecting: isSelecting,
+ itemCount: itemCount,
+ selectedFilters: selectedFilters,
+ );
+
+ return settings.useTvLayout
+ ? _buildTelevisionActions(
+ context: context,
+ isVisible: isVisible,
+ actionDelegate: actionDelegate,
+ )
+ : _buildMobileActions(
+ context: context,
+ isVisible: isVisible,
+ actionDelegate: actionDelegate,
+ );
+ }
+
+ List _buildTelevisionActions({
+ required BuildContext context,
+ required bool Function(ChipSetAction action) isVisible,
+ required AlbumChipSetActionDelegate actionDelegate,
+ }) {
+ return [
+ ...ChipSetActions.general,
+ ].where(isVisible).map((action) {
+ return CaptionedButton(
+ icon: action.getIcon(),
+ caption: action.getText(context),
+ onPressed: () => actionDelegate.onActionSelected(context, {}, action),
+ );
+ }).toList();
+ }
+
+ List _buildMobileActions({
+ required BuildContext context,
+ required bool Function(ChipSetAction action) isVisible,
+ required AlbumChipSetActionDelegate actionDelegate,
+ }) {
return [
if (widget.moveType != null)
IconButton(
@@ -149,7 +197,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
child: PopupMenuButton(
itemBuilder: (context) {
return [
- FilterGridAppBar.toMenuItem(context, ChipSetAction.configureView, enabled: true),
+ ...ChipSetActions.general.where(isVisible).map((action) => FilterGridAppBar.toMenuItem(context, action, enabled: true)),
const PopupMenuDivider(),
FilterGridAppBar.toMenuItem(context, ChipSetAction.toggleTitleSearch, enabled: true),
];
diff --git a/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart
index 9cc912c76..418edc63f 100644
--- a/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart
+++ b/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart
@@ -1,4 +1,5 @@
import 'package:aves/image_providers/app_icon_image_provider.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/basic/query_bar.dart';
@@ -37,8 +38,10 @@ class _AppPickPageState extends State {
@override
Widget build(BuildContext context) {
+ final useTvLayout = settings.useTvLayout;
return Scaffold(
appBar: AppBar(
+ automaticallyImplyLeading: !useTvLayout,
title: Text(context.l10n.appPickDialogTitle),
),
body: SafeArea(
@@ -57,7 +60,7 @@ class _AppPickPageState extends State {
final packages = allPackages.where((package) => package.categoryLauncher).toList()..sort((a, b) => compareAsciiUpperCase(_displayName(a), _displayName(b)));
return Column(
children: [
- QueryBar(queryNotifier: _queryNotifier),
+ if (!useTvLayout) QueryBar(queryNotifier: _queryNotifier),
ValueListenableBuilder(
valueListenable: _queryNotifier,
builder: (context, query, child) {
diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart
index 310d2b382..0a01f5ff1 100644
--- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart
+++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart
@@ -70,6 +70,7 @@ abstract class ChipSetActionDelegate with FeedbackMi
final selectedItemCount = selectedFilters.length;
final hasSelection = selectedFilters.isNotEmpty;
final isMain = appMode == AppMode.main;
+ final useTvLayout = settings.useTvLayout;
switch (action) {
// general
case ChipSetAction.configureView:
@@ -82,9 +83,9 @@ abstract class ChipSetActionDelegate with FeedbackMi
return isSelecting && selectedItemCount == itemCount;
// browsing
case ChipSetAction.search:
- return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
+ return !useTvLayout && appMode.canNavigate && !isSelecting;
case ChipSetAction.toggleTitleSearch:
- return !isSelecting;
+ return !useTvLayout && !isSelecting;
case ChipSetAction.createAlbum:
return false;
// browsing or selecting
diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart
index beb3114f3..7f0e61c5b 100644
--- a/lib/widgets/filter_grids/common/filter_grid_page.dart
+++ b/lib/widgets/filter_grids/common/filter_grid_page.dart
@@ -115,15 +115,23 @@ class FilterGridPage extends StatelessWidget {
);
if (useTvLayout) {
+ final canNavigate = context.select, bool>((v) => v.value.canNavigate);
return Scaffold(
- body: Row(
- children: [
- TvRail(
- controller: context.read(),
- ),
- Expanded(child: body),
- ],
- ),
+ body: canNavigate
+ ? Row(
+ children: [
+ TvRail(
+ controller: context.read(),
+ ),
+ Expanded(child: body),
+ ],
+ )
+ : DirectionalSafeArea(
+ top: false,
+ end: false,
+ bottom: false,
+ child: body,
+ ),
resizeToAvoidBottomInset: false,
extendBody: true,
);
diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart
index 25a94f89e..ed5108c40 100644
--- a/lib/widgets/map/map_page.dart
+++ b/lib/widgets/map/map_page.dart
@@ -277,11 +277,12 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
MapAction.zoomIn,
MapAction.zoomOut,
]
- .map((action) => Padding(
+ .mapIndexed((i, action) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: CaptionedButton(
icon: action.getIcon(),
caption: action.getText(context),
+ autofocus: i == 0,
onPressed: () => MapActionDelegate(_mapController).onActionSelected(context, action),
),
))
diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart
index 9ce560a16..ab6f21f13 100644
--- a/lib/widgets/search/search_delegate.dart
+++ b/lib/widgets/search/search_delegate.dart
@@ -19,6 +19,7 @@ import 'package:aves/model/source/location.dart';
import 'package:aves/model/source/tag.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/widgets/collection/collection_page.dart';
+import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
import 'package:aves/widgets/common/expandable_filter_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
@@ -33,6 +34,14 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
final CollectionSource source;
final CollectionLens? parentCollection;
final ValueNotifier _expandedSectionNotifier = ValueNotifier(null);
+ final FocusNode _suggestionsTopFocusNode = FocusNode();
+ final ScrollController _suggestionsScrollController = ScrollController();
+
+ @override
+ FocusNode? get suggestionsFocusNode => _suggestionsTopFocusNode;
+
+ @override
+ ScrollController get suggestionsScrollController => _suggestionsScrollController;
static const int searchHistoryCount = 10;
static final typeFilters = [
@@ -64,6 +73,14 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
query = initialQuery ?? '';
}
+ @override
+ void dispose() {
+ _expandedSectionNotifier.dispose();
+ _suggestionsTopFocusNode.dispose();
+ _suggestionsScrollController.dispose();
+ super.dispose();
+ }
+
@override
Widget buildSuggestions(BuildContext context) {
final upQuery = query.trim().toUpperCase();
@@ -91,8 +108,12 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
final history = settings.searchHistory.where(notHidden).toList();
return ListView(
+ controller: _suggestionsScrollController,
padding: const EdgeInsets.only(top: 8),
children: [
+ TvEdgeFocus(
+ focusNode: _suggestionsTopFocusNode,
+ ),
_buildFilterRow(
context: context,
filters: [
diff --git a/lib/widgets/settings/navigation/drawer_tab_albums.dart b/lib/widgets/settings/navigation/drawer_tab_albums.dart
index faf8916d8..1b62dcd2a 100644
--- a/lib/widgets/settings/navigation/drawer_tab_albums.dart
+++ b/lib/widgets/settings/navigation/drawer_tab_albums.dart
@@ -1,9 +1,10 @@
import 'package:aves/model/filters/album.dart';
+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/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
-import 'package:aves/widgets/filter_grids/album_pick.dart';
+import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
import 'package:aves/widgets/navigation/drawer/tile.dart';
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
import 'package:flutter/material.dart';
@@ -29,8 +30,10 @@ class _DrawerAlbumTabState extends State {
final source = context.read();
return Column(
children: [
- const DrawerEditorBanner(),
- const Divider(height: 0),
+ if (!settings.useTvLayout) ...[
+ const DrawerEditorBanner(),
+ const Divider(height: 0),
+ ],
Flexible(
child: ReorderableListView.builder(
itemBuilder: (context, index) {
diff --git a/lib/widgets/settings/navigation/drawer_tab_fixed.dart b/lib/widgets/settings/navigation/drawer_tab_fixed.dart
index 0ee54de7b..1069693a3 100644
--- a/lib/widgets/settings/navigation/drawer_tab_fixed.dart
+++ b/lib/widgets/settings/navigation/drawer_tab_fixed.dart
@@ -1,3 +1,4 @@
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
@@ -30,8 +31,10 @@ class _DrawerFixedListTabState extends State> {
Widget build(BuildContext context) {
return Column(
children: [
- const DrawerEditorBanner(),
- const Divider(height: 0),
+ if (!settings.useTvLayout) ...[
+ const DrawerEditorBanner(),
+ const Divider(height: 0),
+ ],
Flexible(
child: ReorderableListView.builder(
itemBuilder: (context, index) {
diff --git a/lib/widgets/settings/video/controls.dart b/lib/widgets/settings/video/controls.dart
index e57b8ef18..d7743500c 100644
--- a/lib/widgets/settings/video/controls.dart
+++ b/lib/widgets/settings/video/controls.dart
@@ -36,6 +36,11 @@ class VideoControlsPage extends StatelessWidget {
onChanged: (v) => settings.videoGestureSideDoubleTapSeek = v,
title: context.l10n.settingsVideoGestureSideDoubleTapSeek,
),
+ SettingsSwitchListTile(
+ selector: (context, s) => s.videoGestureVerticalDragBrightnessVolume,
+ onChanged: (v) => settings.videoGestureVerticalDragBrightnessVolume = v,
+ title: context.l10n.settingsVideoGestureVerticalDragBrightnessVolume,
+ ),
],
),
),
diff --git a/lib/widgets/settings/video/subtitle_sample.dart b/lib/widgets/settings/video/subtitle_sample.dart
index e41ab0848..c8ae248e4 100644
--- a/lib/widgets/settings/video/subtitle_sample.dart
+++ b/lib/widgets/settings/video/subtitle_sample.dart
@@ -5,7 +5,7 @@ import 'package:aves/widgets/common/basic/text/background_painter.dart';
import 'package:aves/widgets/common/basic/text/outlined.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/borders.dart';
-import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
+import 'package:aves/widgets/viewer/visual/video/subtitle/subtitle.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
diff --git a/lib/widgets/stats/mime_donut.dart b/lib/widgets/stats/mime_donut.dart
index f7739612e..4aee3ade5 100644
--- a/lib/widgets/stats/mime_donut.dart
+++ b/lib/widgets/stats/mime_donut.dart
@@ -111,45 +111,37 @@ class _MimeDonutState extends State with AutomaticKeepAliveClientMixi
],
),
);
- final primaryColor = Theme.of(context).colorScheme.primary;
final legend = SizedBox(
width: dim,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: seriesData
- .map((d) => Material(
- type: MaterialType.transparency,
- child: InkResponse(
- onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
- containedInkWell: true,
- highlightShape: BoxShape.rectangle,
- borderRadius: const BorderRadius.all(Radius.circular(123)),
- hoverColor: primaryColor.withOpacity(0.04),
- splashColor: primaryColor.withOpacity(0.12),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(AIcons.disc, color: d.color),
- const SizedBox(width: 8),
- Flexible(
- child: Text(
- d.displayText,
- overflow: TextOverflow.fade,
- softWrap: false,
- maxLines: 1,
- ),
+ .map((d) => InkWell(
+ onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
+ borderRadius: const BorderRadius.all(Radius.circular(123)),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(AIcons.disc, color: d.color),
+ const SizedBox(width: 8),
+ Flexible(
+ child: Text(
+ d.displayText,
+ overflow: TextOverflow.fade,
+ softWrap: false,
+ maxLines: 1,
),
- const SizedBox(width: 8),
- Text(
- numberFormat.format(d.entryCount),
- style: TextStyle(
- color: Theme.of(context).textTheme.bodySmall!.color,
- ),
+ ),
+ const SizedBox(width: 8),
+ Text(
+ numberFormat.format(d.entryCount),
+ style: TextStyle(
+ color: Theme.of(context).textTheme.bodySmall!.color,
),
- const SizedBox(width: 4),
- ],
- ),
+ ),
+ const SizedBox(width: 4),
+ ],
),
))
.toList(),
diff --git a/lib/widgets/stats/stats_page.dart b/lib/widgets/stats/stats_page.dart
index 1edaad08a..85af4999f 100644
--- a/lib/widgets/stats/stats_page.dart
+++ b/lib/widgets/stats/stats_page.dart
@@ -15,6 +15,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/basic/insets.dart';
+import 'package:aves/widgets/common/basic/tv_edge_focus.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_filter_chip.dart';
@@ -96,6 +97,7 @@ class _StatsPageState extends State {
@override
Widget build(BuildContext context) {
+ final useTvLayout = settings.useTvLayout;
return ValueListenableBuilder(
valueListenable: _isPageAnimatingNotifier,
builder: (context, animating, child) {
@@ -196,6 +198,7 @@ class _StatsPageState extends State {
),
),
children: [
+ const TvEdgeFocus(),
mimeDonuts,
Histogram(
entries: entries,
@@ -218,7 +221,7 @@ class _StatsPageState extends State {
return Scaffold(
appBar: AppBar(
- automaticallyImplyLeading: !settings.useTvLayout,
+ automaticallyImplyLeading: !useTvLayout,
title: Text(l10n.statsPageTitle),
),
body: GestureAreaProtectorStack(
@@ -274,23 +277,15 @@ class _StatsPageState extends State {
style: Constants.knownTitleTextStyle,
);
if (settings.useTvLayout) {
- final primaryColor = Theme.of(context).colorScheme.primary;
header = Container(
padding: const EdgeInsets.symmetric(vertical: 12),
alignment: AlignmentDirectional.centerStart,
- child: Material(
- type: MaterialType.transparency,
- child: InkResponse(
- onTap: onHeaderPressed,
- containedInkWell: true,
- highlightShape: BoxShape.rectangle,
- borderRadius: const BorderRadius.all(Radius.circular(123)),
- hoverColor: primaryColor.withOpacity(0.04),
- splashColor: primaryColor.withOpacity(0.12),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: header,
- ),
+ child: InkWell(
+ onTap: onHeaderPressed,
+ borderRadius: const BorderRadius.all(Radius.circular(123)),
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: header,
),
),
);
diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart
index 61577aa3b..1cd1d0a79 100644
--- a/lib/widgets/viewer/action/entry_action_delegate.dart
+++ b/lib/widgets/viewer/action/entry_action_delegate.dart
@@ -31,7 +31,7 @@ import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
import 'package:aves/widgets/dialogs/export_entry_dialog.dart';
-import 'package:aves/widgets/filter_grids/album_pick.dart';
+import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
import 'package:aves/widgets/viewer/action/printer.dart';
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart
index 76c11f35a..e19a65eaf 100644
--- a/lib/widgets/viewer/entry_vertical_pager.dart
+++ b/lib/widgets/viewer/entry_vertical_pager.dart
@@ -138,10 +138,12 @@ class _ViewerVerticalPageViewState extends State {
child: child!,
);
},
- child: InfoPage(
- collection: collection,
- entryNotifier: widget.entryNotifier,
- isScrollingNotifier: _isVerticallyScrollingNotifier,
+ child: FocusScope(
+ child: InfoPage(
+ collection: collection,
+ entryNotifier: widget.entryNotifier,
+ isScrollingNotifier: _isVerticallyScrollingNotifier,
+ ),
),
),
);
@@ -286,10 +288,10 @@ class _ViewerVerticalPageViewState extends State {
final opacity = min(1.0, page);
_backgroundOpacityNotifier.value = opacity * opacity;
- if (page <= 1 && settings.viewerMaxBrightness) {
+ if (settings.viewerMaxBrightness) {
_systemBrightness?.then((system) {
- final transition = max(system, lerpDouble(system, maximumBrightness, page / 2)!);
- ScreenBrightness().setScreenBrightness(transition);
+ final value = lerpDouble(maximumBrightness, system, ((1 - page).abs() * 2).clamp(0, 1))!;
+ ScreenBrightness().setScreenBrightness(value);
});
}
diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart
index 5dc2c3d74..871f1175f 100644
--- a/lib/widgets/viewer/entry_viewer_stack.dart
+++ b/lib/widgets/viewer/entry_viewer_stack.dart
@@ -680,9 +680,7 @@ class _EntryViewerStackState extends State with EntryViewContr
}
Future _onLeave() async {
- if (settings.viewerMaxBrightness) {
- await ScreenBrightness().resetScreenBrightness();
- }
+ await ScreenBrightness().resetScreenBrightness();
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
await windowService.keepScreenOn(false);
}
diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart
index e5b2b5c43..58e56006f 100644
--- a/lib/widgets/viewer/info/info_page.dart
+++ b/lib/widgets/viewer/info/info_page.dart
@@ -7,6 +7,7 @@ import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/basic/insets.dart';
+import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
import 'package:aves/widgets/viewer/embedded/embedded_data_opener.dart';
@@ -281,6 +282,9 @@ class _InfoPageContentState extends State<_InfoPageContent> {
child: CustomScrollView(
controller: widget.scrollController,
slivers: [
+ const SliverToBoxAdapter(
+ child: TvEdgeFocus(),
+ ),
InfoAppBar(
entry: entry,
collection: collection,
diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart
index 1a92214f0..7264a8c40 100644
--- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart
+++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart
@@ -22,56 +22,64 @@ import 'package:tuple/tuple.dart';
@immutable
class XmpNamespace extends Equatable {
+ final Map schemaRegistryPrefixes;
final String nsUri, nsPrefix;
final Map rawProps;
@override
List